Merge pull request #233 from loki-project/notifications

Fix Push Notification Bugs
pull/238/head
Niels Andriesse 4 years ago committed by GitHub
commit 3075e82d8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -16,31 +16,36 @@ final class NotificationServiceExtension : UNNotificationServiceExtension {
self.contentHandler = contentHandler self.contentHandler = contentHandler
notificationContent = (request.content.mutableCopy() as? UNMutableNotificationContent) notificationContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
DispatchQueue.main.sync { self.setUpIfNecessary() } // The code using DispatchQueue.main.async { self.setUpIfNecessary(){ Modify the notification content } } will somehow cause a freezing when the second PN comes
if let notificationContent = notificationContent { DispatchQueue.main.sync { self.setUpIfNecessary(){} }
// Modify the notification content here...
let base64EncodedData = notificationContent.userInfo["ENCRYPTED_DATA"] as! String AppReadiness.runNowOrWhenAppDidBecomeReady {
let data = Data(base64Encoded: base64EncodedData)! if let notificationContent = self.notificationContent {
let envelope = try? MessageWrapper.unwrap(data: data) // Modify the notification content here...
let envelopeData = try? envelope?.serializedData() let base64EncodedData = notificationContent.userInfo["ENCRYPTED_DATA"] as! String
let decrypter = SSKEnvironment.shared.messageDecrypter let data = Data(base64Encoded: base64EncodedData)!
if (envelope != nil && envelopeData != nil) { let decrypter = SSKEnvironment.shared.messageDecrypter
decrypter.decryptEnvelope(envelope!, let messageManager = SSKEnvironment.shared.messageManager
envelopeData: envelopeData!, if let envelope = try? MessageWrapper.unwrap(data: data), let data = try? envelope.serializedData() {
successBlock: { result, transaction in let wasReceivedByUD = self.wasReceivedByUD(envelope: envelope)
if (try? SSKProtoEnvelope.parseData(result.envelopeData)) != nil { decrypter.decryptEnvelope(envelope,
self.handleDecryptionResult(result: result, notificationContent: notificationContent, transaction: transaction) envelopeData: data,
} else { successBlock: { result, transaction in
if let envelope = try? SSKProtoEnvelope.parseData(result.envelopeData) {
messageManager.throws_processEnvelope(envelope, plaintextData: result.plaintextData, wasReceivedByUD: wasReceivedByUD, transaction: transaction, serverID: 0)
self.handleDecryptionResult(result: result, notificationContent: notificationContent, transaction: transaction)
} else {
self.completeWithFailure(content: notificationContent)
}
},
failureBlock: {
self.completeWithFailure(content: notificationContent) self.completeWithFailure(content: notificationContent)
} }
}, )
failureBlock: { } else {
self.completeWithFailure(content: notificationContent) self.completeWithFailure(content: notificationContent)
} }
)
} else {
self.completeWithFailure(content: notificationContent)
} }
} }
} }
@ -49,31 +54,31 @@ final class NotificationServiceExtension : UNNotificationServiceExtension {
let contentProto = try? SSKProtoContent.parseData(result.plaintextData!) let contentProto = try? SSKProtoContent.parseData(result.plaintextData!)
var thread: TSThread var thread: TSThread
var newNotificationBody = "" var newNotificationBody = ""
let masterHexEncodedPublicKey = OWSPrimaryStorage.shared().getMasterHexEncodedPublicKey(for: result.source, in: transaction) ?? result.source let masterPublicKey = OWSPrimaryStorage.shared().getMasterHexEncodedPublicKey(for: result.source, in: transaction) ?? result.source
var displayName = masterHexEncodedPublicKey var displayName = masterPublicKey
if let groupID = contentProto?.dataMessage?.group?.id { if let groupID = contentProto?.dataMessage?.group?.id {
thread = TSGroupThread.getOrCreateThread(withGroupId: groupID, groupType: .closedGroup, transaction: transaction) thread = TSGroupThread.getOrCreateThread(withGroupId: groupID, groupType: .closedGroup, transaction: transaction)
displayName = thread.name() displayName = thread.name()
if displayName.count < 1 { if displayName.count < 1 {
displayName = MessageStrings.newGroupDefaultTitle displayName = MessageStrings.newGroupDefaultTitle
} }
let group: SSKProtoGroupContext = (contentProto?.dataMessage?.group!)! let group: SSKProtoGroupContext = contentProto!.dataMessage!.group!
let oldGroupModel = (thread as! TSGroupThread).groupModel let oldGroupModel = (thread as! TSGroupThread).groupModel
var removeMembers = Set(arrayLiteral: oldGroupModel.groupMemberIds) var removedMembers = Set(arrayLiteral: oldGroupModel.groupMemberIds)
let newGroupModel = TSGroupModel.init(title: group.name, let newGroupModel = TSGroupModel.init(title: group.name,
memberIds:group.members, memberIds:group.members,
image: oldGroupModel.groupImage, image: oldGroupModel.groupImage,
groupId: group.id, groupId: group.id,
groupType: oldGroupModel.groupType, groupType: oldGroupModel.groupType,
adminIds: group.admins) adminIds: group.admins)
removeMembers.subtract(Set(arrayLiteral: newGroupModel.groupMemberIds)) removedMembers.subtract(Set(arrayLiteral: newGroupModel.groupMemberIds))
newGroupModel.removedMembers = NSMutableSet(set: removeMembers) newGroupModel.removedMembers = NSMutableSet(set: removedMembers)
switch contentProto?.dataMessage?.group?.type { switch contentProto?.dataMessage?.group?.type {
case .update: case .update:
newNotificationBody = oldGroupModel.getInfoStringAboutUpdate(to: newGroupModel, contactsManager: SSKEnvironment.shared.contactsManager) newNotificationBody = oldGroupModel.getInfoStringAboutUpdate(to: newGroupModel, contactsManager: SSKEnvironment.shared.contactsManager)
break break
case .quit: case .quit:
let nameString = SSKEnvironment.shared.contactsManager.displayName(forPhoneIdentifier: masterHexEncodedPublicKey, transaction: transaction) let nameString = SSKEnvironment.shared.contactsManager.displayName(forPhoneIdentifier: masterPublicKey, transaction: transaction)
newNotificationBody = NSLocalizedString("GROUP_MEMBER_LEFT", comment: nameString) newNotificationBody = NSLocalizedString("GROUP_MEMBER_LEFT", comment: nameString)
break break
default: default:
@ -88,7 +93,7 @@ final class NotificationServiceExtension : UNNotificationServiceExtension {
notificationContent.userInfo = userInfo notificationContent.userInfo = userInfo
notificationContent.badge = 1 notificationContent.badge = 1
if newNotificationBody.count < 1 { if newNotificationBody.count < 1 {
newNotificationBody = contentProto?.dataMessage?.body ?? "" newNotificationBody = contentProto?.dataMessage?.body ?? "You've got a new message"
} }
notificationContent.body = newNotificationBody notificationContent.body = newNotificationBody
if notificationContent.body.count < 1 { if notificationContent.body.count < 1 {
@ -98,7 +103,7 @@ final class NotificationServiceExtension : UNNotificationServiceExtension {
} }
} }
func setUpIfNecessary() { func setUpIfNecessary(completion: @escaping () -> Void) {
AssertIsOnMainThread() AssertIsOnMainThread()
// The NSE will often re-use the same process, so if we're // The NSE will often re-use the same process, so if we're
@ -116,8 +121,6 @@ final class NotificationServiceExtension : UNNotificationServiceExtension {
DebugLogger.shared().enableFileLogging() DebugLogger.shared().enableFileLogging()
} }
Logger.info("")
_ = AppVersion.sharedInstance() _ = AppVersion.sharedInstance()
Cryptography.seedRandom() Cryptography.seedRandom()
@ -131,12 +134,12 @@ final class NotificationServiceExtension : UNNotificationServiceExtension {
AppSetup.setupEnvironment( AppSetup.setupEnvironment(
appSpecificSingletonBlock: { appSpecificSingletonBlock: {
// TODO: calls..
SSKEnvironment.shared.callMessageHandler = NoopCallMessageHandler() SSKEnvironment.shared.callMessageHandler = NoopCallMessageHandler()
SSKEnvironment.shared.notificationsManager = NoopNotificationsManager() SSKEnvironment.shared.notificationsManager = NoopNotificationsManager()
}, },
migrationCompletion: { [weak self] in migrationCompletion: { [weak self] in
self?.versionMigrationsDidComplete() self?.versionMigrationsDidComplete()
completion()
} }
) )
@ -162,8 +165,6 @@ final class NotificationServiceExtension : UNNotificationServiceExtension {
func versionMigrationsDidComplete() { func versionMigrationsDidComplete() {
AssertIsOnMainThread() AssertIsOnMainThread()
Logger.debug("")
areVersionMigrationsComplete = true areVersionMigrationsComplete = true
checkIsAppReady() checkIsAppReady()
@ -173,8 +174,6 @@ final class NotificationServiceExtension : UNNotificationServiceExtension {
func storageIsReady() { func storageIsReady() {
AssertIsOnMainThread() AssertIsOnMainThread()
Logger.debug("")
checkIsAppReady() checkIsAppReady()
} }
@ -197,7 +196,7 @@ final class NotificationServiceExtension : UNNotificationServiceExtension {
} }
func completeWithFailure(content: UNMutableNotificationContent) { func completeWithFailure(content: UNMutableNotificationContent) {
content.body = "You've got a new message." content.body = "You've got a new message"
content.title = "Session" content.title = "Session"
let userInfo: [String:Any] = [NotificationServiceExtension.isFromRemoteKey : true] let userInfo: [String:Any] = [NotificationServiceExtension.isFromRemoteKey : true]
content.userInfo = userInfo content.userInfo = userInfo

@ -12,7 +12,7 @@ final class NotificationServiceExtensionContext : NSObject, AppContext {
let isMainAppAndActive = false let isMainAppAndActive = false
var openSystemSettingsAction: UIAlertAction? var openSystemSettingsAction: UIAlertAction?
var wasWokenUpBySilentPushNotification = true var wasWokenUpByPushNotification = true
var shouldProcessIncomingMessages: Bool { true } var shouldProcessIncomingMessages: Bool { true }

@ -61,8 +61,6 @@ static NSTimeInterval launchStartedAt;
@property (nonatomic) BOOL hasInitialRootViewController; @property (nonatomic) BOOL hasInitialRootViewController;
@property (nonatomic) BOOL areVersionMigrationsComplete; @property (nonatomic) BOOL areVersionMigrationsComplete;
@property (nonatomic) BOOL didAppLaunchFail; @property (nonatomic) BOOL didAppLaunchFail;
// Loki
@property (nonatomic) LKPoller *poller; @property (nonatomic) LKPoller *poller;
@property (nonatomic) LKClosedGroupPoller *closedGroupPoller; @property (nonatomic) LKClosedGroupPoller *closedGroupPoller;
@ -160,7 +158,7 @@ static NSTimeInterval launchStartedAt;
return AppEnvironment.shared.legacyNotificationActionHandler; return AppEnvironment.shared.legacyNotificationActionHandler;
} }
#pragma mark - #pragma mark - Lifecycle
- (void)applicationDidEnterBackground:(UIApplication *)application - (void)applicationDidEnterBackground:(UIApplication *)application
{ {
@ -297,6 +295,94 @@ static NSTimeInterval launchStartedAt;
return YES; return YES;
} }
- (void)applicationDidBecomeActive:(UIApplication *)application {
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"App launch failed");
return;
}
if (CurrentAppContext().isRunningTests) {
return;
}
[self ensureRootViewController];
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
[self handleActivation];
}];
// Clear all notifications whenever we become active.
// When opening the app from a notification,
// AppDelegate.didReceiveLocalNotification will always
// be called _before_ we become active.
[self clearAllNotificationsAndRestoreBadgeCount];
// On every activation, clear old temp directories.
ClearOldTemporaryDirectories();
}
- (void)applicationWillResignActive:(UIApplication *)application
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"App launch failed");
return;
}
[self clearAllNotificationsAndRestoreBadgeCount];
[DDLog flushLog];
}
#pragma mark - Orientation
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(nullable UIWindow *)window
{
return UIInterfaceOrientationMaskPortrait;
}
#pragma mark - Background Fetching
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
{
NSLog(@"[Loki] Performing background fetch.");
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
NSMutableArray *promises = [NSMutableArray new];
__block AnyPromise *fetchMessagesPromise = [AppEnvironment.shared.messageFetcherJob run].then(^{
fetchMessagesPromise = nil;
}).catch(^{
fetchMessagesPromise = nil;
});
[promises addObject:fetchMessagesPromise];
[fetchMessagesPromise retainUntilComplete];
__block NSDictionary<NSString *, LKPublicChat *> *publicChats;
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
publicChats = [LKDatabaseUtilities getAllPublicChats:transaction];
}];
for (LKPublicChat *publicChat in publicChats) {
if (![publicChat isKindOfClass:LKPublicChat.class]) { continue; }
LKPublicChatPoller *poller = [[LKPublicChatPoller alloc] initForPublicChat:publicChat];
[poller stop];
AnyPromise *fetchGroupMessagesPromise = [poller pollForNewMessages];
[promises addObject:fetchGroupMessagesPromise];
[fetchGroupMessagesPromise retainUntilComplete];
}
PMKJoin(promises).then(^(id results) {
completionHandler(UIBackgroundFetchResultNewData);
}).catch(^(id error) {
completionHandler(UIBackgroundFetchResultFailed);
});
}];
}
#pragma mark - App Readiness
/** /**
* The user must unlock the device once after reboot before the database encryption key can be accessed. * The user must unlock the device once after reboot before the database encryption key can be accessed.
*/ */
@ -395,86 +481,6 @@ static NSTimeInterval launchStartedAt;
OWSLogInfo(@"Build Date/Time: %@", buildDetails[@"DateTime"]); OWSLogInfo(@"Build Date/Time: %@", buildDetails[@"DateTime"]);
} }
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"App launch failed");
return;
}
[self.pushRegistrationManager didReceiveVanillaPushToken:deviceToken];
OWSLogInfo(@"Registering for push notifications with token: %@.", deviceToken);
BOOL isUsingFullAPNs = [NSUserDefaults.standardUserDefaults boolForKey:@"isUsingFullAPNs"];
if (isUsingFullAPNs) {
__unused AnyPromise *promise = [LKPushNotificationManager registerWithToken:deviceToken hexEncodedPublicKey:self.tsAccountManager.localNumber isForcedUpdate:NO];
} else {
__unused AnyPromise *promise = [LKPushNotificationManager registerWithToken:deviceToken isForcedUpdate:NO];
}
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"App launch failed");
return;
}
OWSLogError(@"Failed to register push token with error: %@.", error);
#ifdef DEBUG
OWSLogWarn(@"We're in debug mode. Faking success for remote registration with a fake push identifier.");
[self.pushRegistrationManager didReceiveVanillaPushToken:[[NSMutableData dataWithLength:32] copy]];
#else
OWSProdError([OWSAnalyticsEvents appDelegateErrorFailedToRegisterForRemoteNotifications]);
[self.pushRegistrationManager didFailToReceiveVanillaPushTokenWithError:error];
#endif
}
- (void)application:(UIApplication *)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"App launch failed");
return;
}
[self.notificationPresenter didRegisterLegacyNotificationSettings];
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"App launch failed");
return;
}
if (CurrentAppContext().isRunningTests) {
return;
}
[self ensureRootViewController];
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
[self handleActivation];
}];
// Clear all notifications whenever we become active.
// When opening the app from a notification,
// AppDelegate.didReceiveLocalNotification will always
// be called _before_ we become active.
[self clearAllNotificationsAndRestoreBadgeCount];
// On every activation, clear old temp directories.
ClearOldTemporaryDirectories();
}
- (void)enableBackgroundRefreshIfNecessary - (void)enableBackgroundRefreshIfNecessary
{ {
[AppReadiness runNowOrWhenAppDidBecomeReady:^{ [AppReadiness runNowOrWhenAppDidBecomeReady:^{
@ -536,7 +542,7 @@ static NSTimeInterval launchStartedAt;
[self.socketManager requestSocketOpen]; [self.socketManager requestSocketOpen];
[Environment.shared.contactsManager fetchSystemContactsOnceIfAlreadyAuthorized]; [Environment.shared.contactsManager fetchSystemContactsOnceIfAlreadyAuthorized];
NSString *userHexEncodedPublicKey = self.tsAccountManager.localNumber; NSString *userPublicKey = self.tsAccountManager.localNumber;
[self startPollerIfNeeded]; [self startPollerIfNeeded];
[self startClosedGroupPollerIfNeeded]; [self startClosedGroupPollerIfNeeded];
@ -548,15 +554,15 @@ static NSTimeInterval launchStartedAt;
NSDate *lastProfilePictureUpload = (NSDate *)[userDefaults objectForKey:@"lastProfilePictureUpload"]; NSDate *lastProfilePictureUpload = (NSDate *)[userDefaults objectForKey:@"lastProfilePictureUpload"];
if (lastProfilePictureUpload != nil && [now timeIntervalSinceDate:lastProfilePictureUpload] > 4 * 24 * 60 * 60) { if (lastProfilePictureUpload != nil && [now timeIntervalSinceDate:lastProfilePictureUpload] > 4 * 24 * 60 * 60) {
OWSProfileManager *profileManager = OWSProfileManager.sharedManager; OWSProfileManager *profileManager = OWSProfileManager.sharedManager;
NSString *displayName = [profileManager profileNameForRecipientWithID:userHexEncodedPublicKey]; NSString *displayName = [profileManager profileNameForRecipientWithID:userPublicKey];
UIImage *profilePicture = [profileManager profileAvatarForRecipientId:userHexEncodedPublicKey]; UIImage *profilePicture = [profileManager profileAvatarForRecipientId:userPublicKey];
[profileManager updateLocalProfileName:displayName avatarImage:profilePicture success:^{ [profileManager updateLocalProfileName:displayName avatarImage:profilePicture success:^{
// Do nothing; the user defaults flag is updated in LokiFileServerAPI // Do nothing; the user defaults flag is updated in LokiFileServerAPI
} failure:^(NSError *error) { } failure:^(NSError *error) {
// Do nothing // Do nothing
} requiresSync:YES]; } requiresSync:YES];
} }
if (![UIApplication sharedApplication].isRegisteredForRemoteNotifications) { if (![UIApplication sharedApplication].isRegisteredForRemoteNotifications) {
OWSLogInfo(@"Retrying remote notification registration since user hasn't registered yet."); OWSLogInfo(@"Retrying remote notification registration since user hasn't registered yet.");
// Push tokens don't normally change while the app is launched, so checking once during launch is // Push tokens don't normally change while the app is launched, so checking once during launch is
@ -571,211 +577,10 @@ static NSTimeInterval launchStartedAt;
} }
} }
- (void)applicationWillResignActive:(UIApplication *)application
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"App launch failed");
return;
}
[self clearAllNotificationsAndRestoreBadgeCount];
[DDLog flushLog];
}
- (void)clearAllNotificationsAndRestoreBadgeCount
{
OWSAssertIsOnMainThread();
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
[AppEnvironment.shared.notificationPresenter clearAllNotifications];
[OWSMessageUtils.sharedManager updateApplicationBadgeCount];
}];
}
- (void)application:(UIApplication *)application
performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem
completionHandler:(void (^)(BOOL succeeded))completionHandler {
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"App launch failed");
completionHandler(NO);
return;
}
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
if (![self.tsAccountManager isRegisteredAndReady]) {
UIAlertController *controller =
[UIAlertController alertControllerWithTitle:NSLocalizedString(@"REGISTER_CONTACTS_WELCOME", nil)
message:NSLocalizedString(@"REGISTRATION_RESTRICTED_MESSAGE", nil)
preferredStyle:UIAlertControllerStyleAlert];
[controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action){
}]];
UIViewController *fromViewController = [[UIApplication sharedApplication] frontmostViewController];
[fromViewController presentViewController:controller
animated:YES
completion:^{
completionHandler(NO);
}];
return;
}
[SignalApp.sharedApp.homeViewController createNewPrivateChat];
completionHandler(YES);
}];
}
#pragma mark - Orientation
- (UIInterfaceOrientationMask)application:(UIApplication *)application
supportedInterfaceOrientationsForWindow:(nullable UIWindow *)window
{
return UIInterfaceOrientationMaskPortrait;
}
#pragma mark Push Notifications Delegate Methods
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"App launch failed");
return;
}
OWSLogInfo(@"%@", notification);
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
if (![self.tsAccountManager isRegisteredAndReady]) {
OWSLogInfo(@"Ignoring action; app not ready.");
return;
}
[self.legacyNotificationActionHandler
handleNotificationResponseWithActionIdentifier:OWSLegacyNotificationActionHandler.kDefaultActionIdentifier
notification:notification
responseInfo:@{}
completionHandler:^{
}];
}];
}
- (void)application:(UIApplication *)application
handleActionWithIdentifier:(NSString *)identifier
forLocalNotification:(UILocalNotification *)notification
completionHandler:(void (^)())completionHandler
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"App launch failed");
completionHandler();
return;
}
// The docs for handleActionWithIdentifier:... state:
// "You must call [completionHandler] at the end of your method.".
// Nonetheless, it is presumably safe to call the completion handler
// later, after this method returns.
//
// https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623068-application?language=objc
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
if (![self.tsAccountManager isRegisteredAndReady]) {
OWSLogInfo(@"Ignoring action; app not ready.");
completionHandler();
return;
}
[self.legacyNotificationActionHandler handleNotificationResponseWithActionIdentifier:identifier
notification:notification
responseInfo:@{}
completionHandler:completionHandler];
}];
}
- (void)application:(UIApplication *)application
handleActionWithIdentifier:(NSString *)identifier
forLocalNotification:(UILocalNotification *)notification
withResponseInfo:(NSDictionary *)responseInfo
completionHandler:(void (^)())completionHandler
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"App launch failed");
completionHandler();
return;
}
// The docs for handleActionWithIdentifier:... state:
// "You must call [completionHandler] at the end of your method.".
// Nonetheless, it is presumably safe to call the completion handler
// later, after this method returns.
//
// https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623068-application?language=objc
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
if (![self.tsAccountManager isRegisteredAndReady]) {
OWSLogInfo(@"Ignoring action; app not ready.");
completionHandler();
return;
}
[self.legacyNotificationActionHandler handleNotificationResponseWithActionIdentifier:identifier
notification:notification
responseInfo:responseInfo
completionHandler:completionHandler];
}];
}
- (void)application:(UIApplication *)application
performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
{
NSLog(@"[Loki] Performing background fetch.");
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
NSMutableArray *promises = [NSMutableArray new];
__block AnyPromise *fetchMessagesPromise = [AppEnvironment.shared.messageFetcherJob run].then(^{
fetchMessagesPromise = nil;
}).catch(^{
fetchMessagesPromise = nil;
});
[promises addObject:fetchMessagesPromise];
[fetchMessagesPromise retainUntilComplete];
__block NSDictionary<NSString *, LKPublicChat *> *publicChats;
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
publicChats = [LKDatabaseUtilities getAllPublicChats:transaction];
}];
for (LKPublicChat *publicChat in publicChats) {
if (![publicChat isKindOfClass:LKPublicChat.class]) { continue; } // For some reason publicChat is sometimes a base 64 encoded string...
LKPublicChatPoller *poller = [[LKPublicChatPoller alloc] initForPublicChat:publicChat];
[poller stop];
AnyPromise *fetchGroupMessagesPromise = [poller pollForNewMessages];
[promises addObject:fetchGroupMessagesPromise];
[fetchGroupMessagesPromise retainUntilComplete];
}
PMKJoin(promises).then(^(id results) {
completionHandler(UIBackgroundFetchResultNewData);
}).catch(^(id error) {
completionHandler(UIBackgroundFetchResultFailed);
});
}];
}
- (void)versionMigrationsDidComplete - (void)versionMigrationsDidComplete
{ {
OWSAssertIsOnMainThread(); OWSAssertIsOnMainThread();
OWSLogInfo(@"versionMigrationsDidComplete");
self.areVersionMigrationsComplete = YES; self.areVersionMigrationsComplete = YES;
[self checkIfAppIsReady]; [self checkIfAppIsReady];
@ -784,7 +589,6 @@ static NSTimeInterval launchStartedAt;
- (void)storageIsReady - (void)storageIsReady
{ {
OWSAssertIsOnMainThread(); OWSAssertIsOnMainThread();
OWSLogInfo(@"storageIsReady");
[self checkIfAppIsReady]; [self checkIfAppIsReady];
} }
@ -956,7 +760,7 @@ static NSTimeInterval launchStartedAt;
[UIViewController attemptRotationToDeviceOrientation]; [UIViewController attemptRotationToDeviceOrientation];
} }
#pragma mark - status bar touches #pragma mark - Status Bar Interaction
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{ {
@ -968,16 +772,93 @@ static NSTimeInterval launchStartedAt;
} }
} }
#pragma mark - UNUserNotificationsDelegate #pragma mark - Notifications
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"App launch failed");
return;
}
[self.pushRegistrationManager didReceiveVanillaPushToken:deviceToken];
OWSLogInfo(@"Registering for push notifications with token: %@.", deviceToken);
BOOL isUsingFullAPNs = [NSUserDefaults.standardUserDefaults boolForKey:@"isUsingFullAPNs"];
if (isUsingFullAPNs) {
__unused AnyPromise *promise = [LKPushNotificationManager registerWithToken:deviceToken hexEncodedPublicKey:self.tsAccountManager.localNumber isForcedUpdate:NO];
} else {
__unused AnyPromise *promise = [LKPushNotificationManager registerWithToken:deviceToken isForcedUpdate:NO];
}
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"App launch failed");
return;
}
OWSLogError(@"Failed to register push token with error: %@.", error);
#ifdef DEBUG
OWSLogWarn(@"We're in debug mode. Faking success for remote registration with a fake push identifier.");
[self.pushRegistrationManager didReceiveVanillaPushToken:[[NSMutableData dataWithLength:32] copy]];
#else
OWSProdError([OWSAnalyticsEvents appDelegateErrorFailedToRegisterForRemoteNotifications]);
[self.pushRegistrationManager didFailToReceiveVanillaPushTokenWithError:error];
#endif
}
- (void)application:(UIApplication *)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"App launch failed");
return;
}
[self.notificationPresenter didRegisterLegacyNotificationSettings];
}
- (void)clearAllNotificationsAndRestoreBadgeCount
{
OWSAssertIsOnMainThread();
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
[AppEnvironment.shared.notificationPresenter clearAllNotifications];
[OWSMessageUtils.sharedManager updateApplicationBadgeCount];
}];
}
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"App launch failed");
completionHandler(NO);
return;
}
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
if (![self.tsAccountManager isRegisteredAndReady]) { return; }
[SignalApp.sharedApp.homeViewController createNewPrivateChat];
completionHandler(YES);
}];
}
// The method will be called on the delegate only if the application is in the foreground. If the method is not // 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 // 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. // 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. // This decision should be based on whether the information in the notification is otherwise visible to the user.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
willPresentNotification:(UNNotification *)notification __IOS_AVAILABLE(10.0)__TVOS_AVAILABLE(10.0)__WATCHOS_AVAILABLE(3.0)__OSX_AVAILABLE(10.14)
withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
__IOS_AVAILABLE(10.0)__TVOS_AVAILABLE(10.0)__WATCHOS_AVAILABLE(3.0)__OSX_AVAILABLE(10.14)
{ {
if (notification.request.content.userInfo[@"remote"]) { if (notification.request.content.userInfo[@"remote"]) {
OWSLogInfo(@"[Loki] Ignoring remote notifications while the app is in the foreground."); OWSLogInfo(@"[Loki] Ignoring remote notifications while the app is in the foreground.");
@ -989,8 +870,7 @@ static NSTimeInterval launchStartedAt;
// need to handle this behavior for legacy UINotification users anyway, we "allow" all // need to handle this behavior for legacy UINotification users anyway, we "allow" all
// notification options here, and rely on the shared logic in NotificationPresenter to // notification options here, and rely on the shared logic in NotificationPresenter to
// honor notification sound preferences for both modern and legacy users. // honor notification sound preferences for both modern and legacy users.
UNNotificationPresentationOptions options = UNNotificationPresentationOptionAlert UNNotificationPresentationOptions options = UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound;
| UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound;
completionHandler(options); completionHandler(options);
}]; }];
} }
@ -998,10 +878,8 @@ static NSTimeInterval launchStartedAt;
// The method will be called on the delegate when the user responded to the notification by opening the application, // 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 // dismissing the notification or choosing a UNNotificationAction. The delegate must be set before the application
// returns from application:didFinishLaunchingWithOptions:. // returns from application:didFinishLaunchingWithOptions:.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler __IOS_AVAILABLE(10.0)__WATCHOS_AVAILABLE(3.0)
didReceiveNotificationResponse:(UNNotificationResponse *)response __OSX_AVAILABLE(10.14)__TVOS_PROHIBITED
withCompletionHandler:(void (^)(void))completionHandler __IOS_AVAILABLE(10.0)__WATCHOS_AVAILABLE(3.0)
__OSX_AVAILABLE(10.14)__TVOS_PROHIBITED
{ {
[AppReadiness runNowOrWhenAppDidBecomeReady:^() { [AppReadiness runNowOrWhenAppDidBecomeReady:^() {
[self.userNotificationActionHandler handleNotificationResponse:response completionHandler:completionHandler]; [self.userNotificationActionHandler handleNotificationResponse:response completionHandler:completionHandler];
@ -1012,14 +890,13 @@ static NSTimeInterval launchStartedAt;
// in-app notification settings. Add UNAuthorizationOptionProvidesAppNotificationSettings as an option in // in-app notification settings. Add UNAuthorizationOptionProvidesAppNotificationSettings as an option in
// requestAuthorizationWithOptions:completionHandler: to add a button to inline notification settings view and the // 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. // notification settings view in Settings. The notification will be nil when opened from Settings.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center - (void)userNotificationCenter:(UNUserNotificationCenter *)center openSettingsForNotification:(nullable UNNotification *)notification __IOS_AVAILABLE(12.0)
openSettingsForNotification:(nullable UNNotification *)notification __IOS_AVAILABLE(12.0) __OSX_AVAILABLE(10.14)__WATCHOS_PROHIBITED __TVOS_PROHIBITED
__OSX_AVAILABLE(10.14)__WATCHOS_PROHIBITED __TVOS_PROHIBITED
{ {
} }
#pragma mark - Loki #pragma mark - Polling
- (void)startPollerIfNeeded - (void)startPollerIfNeeded
{ {
@ -1055,7 +932,10 @@ static NSTimeInterval launchStartedAt;
- (void)stopOpenGroupPollers { [LKPublicChatManager.shared stopPollers]; } - (void)stopOpenGroupPollers { [LKPublicChatManager.shared stopPollers]; }
- (void)handleDataNukeRequested:(NSNotification *)notification { # pragma mark - Other
- (void)handleDataNukeRequested:(NSNotification *)notification
{
[ThreadUtil deleteAllContent]; [ThreadUtil deleteAllContent];
[SSKEnvironment.shared.messageSenderJobQueue clearAllJobs]; [SSKEnvironment.shared.messageSenderJobQueue clearAllJobs];
[SSKEnvironment.shared.identityManager clearIdentityKey]; [SSKEnvironment.shared.identityManager clearIdentityKey];

@ -212,12 +212,16 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
isObservingDatabase = isViewVisible && CurrentAppContext().isAppForegroundAndActive() isObservingDatabase = isViewVisible && CurrentAppContext().isAppForegroundAndActive()
} }
private func reload() { private func updateThreadMapping() {
AssertIsOnMainThread()
uiDatabaseConnection.beginLongLivedReadTransaction() uiDatabaseConnection.beginLongLivedReadTransaction()
uiDatabaseConnection.read { transaction in uiDatabaseConnection.read { transaction in
self.threads.update(with: transaction) self.threads.update(with: transaction)
} }
}
private func reload() {
AssertIsOnMainThread()
updateThreadMapping()
threadViewModelCache.removeAll() threadViewModelCache.removeAll()
tableView.reloadData() tableView.reloadData()
emptyStateView.isHidden = (threadCount != 0) emptyStateView.isHidden = (threadCount != 0)
@ -235,6 +239,21 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
} }
return return
} }
// Guard for changes made in Notification Service Extension
// Crashes may happen if there is some modification in NSE
// The YapDBModificationNotification cannot cross process
// So the thread mapping won't update itself if DB modification happened in other process like NSE
// With these code we can sync the mapping before asking for changes from YapDB
if (notifications.count > 0) {
if let firstChangeset = notifications[0].userInfo {
let firstSnapshot = firstChangeset[YapDatabaseSnapshotKey] as! UInt64
if (self.threads.snapshotOfLastUpdate != firstSnapshot - 1) {
reload()
return
}
}
}
var sectionChanges = NSArray() var sectionChanges = NSArray()
var rowChanges = NSArray() var rowChanges = NSArray()
ext.getSectionChanges(&sectionChanges, rowChanges: &rowChanges, for: notifications, with: threads) ext.getSectionChanges(&sectionChanges, rowChanges: &rowChanges, for: notifications, with: threads)
@ -261,6 +280,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
@objc private func handleApplicationDidBecomeActiveNotification(_ notification: Notification) { @objc private func handleApplicationDidBecomeActiveNotification(_ notification: Notification) {
updateIsObservingDatabase() updateIsObservingDatabase()
updateThreadMapping()
} }
@objc private func handleApplicationWillResignActiveNotification(_ notification: Notification) { @objc private func handleApplicationWillResignActiveNotification(_ notification: Notification) {

@ -85,7 +85,7 @@ extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee {
self.notificationCenter.setNotificationCategories(UserNotificationConfig.allNotificationCategories) self.notificationCenter.setNotificationCategories(UserNotificationConfig.allNotificationCategories)
if granted { if granted {
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 {

@ -28,7 +28,7 @@ NSString *const ReportedApplicationStateDidChangeNotification = @"ReportedApplic
@synthesize mainWindow = _mainWindow; @synthesize mainWindow = _mainWindow;
@synthesize appLaunchTime = _appLaunchTime; @synthesize appLaunchTime = _appLaunchTime;
@synthesize wasWokenUpBySilentPushNotification = _wasWokenUpBySilentPushNotification; @synthesize wasWokenUpByPushNotification = _wasWokenUpByPushNotification;
- (instancetype)init - (instancetype)init
{ {
@ -41,7 +41,7 @@ NSString *const ReportedApplicationStateDidChangeNotification = @"ReportedApplic
self.reportedApplicationState = UIApplicationStateInactive; self.reportedApplicationState = UIApplicationStateInactive;
_appLaunchTime = [NSDate new]; _appLaunchTime = [NSDate new];
_wasWokenUpBySilentPushNotification = false; _wasWokenUpByPushNotification = false;
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillEnterForeground:) selector:@selector(applicationWillEnterForeground:)

@ -264,7 +264,7 @@ typedef void (^AttachmentDownloadFailure)(NSError *error);
- (void)startDownloadIfPossible - (void)startDownloadIfPossible
{ {
if (CurrentAppContext().wasWokenUpBySilentPushNotification) { return; } if (CurrentAppContext().wasWokenUpByPushNotification) { return; }
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OWSAttachmentDownloadJob *_Nullable job; OWSAttachmentDownloadJob *_Nullable job;

@ -1613,10 +1613,10 @@ NS_ASSUME_NONNULL_BEGIN
// Update thread preview in inbox // Update thread preview in inbox
[masterThread touchWithTransaction:transaction]; [masterThread touchWithTransaction:transaction];
[SSKEnvironment.shared.notificationsManager notifyUserForIncomingMessage:incomingMessage if (CurrentAppContext().isMainAppAndActive) {
inThread:masterThread [SSKEnvironment.shared.notificationsManager notifyUserForIncomingMessage:incomingMessage inThread:masterThread transaction:transaction];
transaction:transaction]; }
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.typingIndicators didReceiveIncomingMessageInThread:masterThread [self.typingIndicators didReceiveIncomingMessageInThread:masterThread

@ -88,8 +88,6 @@ NSString *const kSessionStoreDBConnectionKey = @"kSessionStoreDBConnectionKey";
OWSAssertDebug(contactIdentifier.length > 0); OWSAssertDebug(contactIdentifier.length > 0);
OWSAssertDebug(deviceId >= 0); OWSAssertDebug(deviceId >= 0);
OWSAssertDebug([protocolContext isKindOfClass:[YapDatabaseReadWriteTransaction class]]); OWSAssertDebug([protocolContext isKindOfClass:[YapDatabaseReadWriteTransaction class]]);
// FIXME: This needs a comment from Ryan explaining why it's necessary (it has to do with push notifications)
if (!CurrentAppContext().isMainApp) { return; }
YapDatabaseReadWriteTransaction *transaction = protocolContext; YapDatabaseReadWriteTransaction *transaction = protocolContext;

@ -39,7 +39,7 @@ NSString *NSStringForUIApplicationState(UIApplicationState value);
@property (nonatomic, readonly) BOOL isMainAppAndActive; @property (nonatomic, readonly) BOOL isMainAppAndActive;
/// Whether the app was woken up by a silent push notification. This is important for /// Whether the app was woken up by a silent push notification. This is important for
/// determining whether attachments should be downloaded or not. /// determining whether attachments should be downloaded or not.
@property (nonatomic) BOOL wasWokenUpBySilentPushNotification; @property (nonatomic) BOOL wasWokenUpByPushNotification;
// Whether the user is using a right-to-left language like Arabic. // Whether the user is using a right-to-left language like Arabic.
@property (nonatomic, readonly) BOOL isRTL; @property (nonatomic, readonly) BOOL isRTL;

Loading…
Cancel
Save