session-ios/Signal/src/AppDelegate.m

1075 lines
41 KiB
Mathematica
Raw Normal View History

2017-01-17 22:01:19 +01:00
//
2019-01-08 17:47:40 +01:00
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
2017-01-17 22:01:19 +01:00
//
2014-05-06 19:41:08 +02:00
#import "AppDelegate.h"
#import "DebugLogger.h"
2017-11-29 20:51:39 +01:00
#import "MainAppContext.h"
2018-03-06 16:10:22 +01:00
#import "OWSBackup.h"
2018-08-02 21:18:40 +02:00
#import "OWSOrphanDataCleaner.h"
2018-03-21 20:46:23 +01:00
#import "OWSScreenLockUI.h"
2017-03-23 14:55:39 +01:00
#import "Pastelog.h"
2019-05-02 23:58:48 +02:00
#import "Session-Swift.h"
#import "SignalApp.h"
#import "SignalsNavigationController.h"
#import "ViewControllerUtils.h"
#import <PromiseKit/AnyPromise.h>
2020-06-05 02:38:44 +02:00
#import <SessionCoreKit/iOSVersions.h>
2017-12-07 19:53:13 +01:00
#import <SignalMessaging/AppSetup.h>
2017-12-19 03:50:51 +01:00
#import <SignalMessaging/Environment.h>
#import <SignalMessaging/OWSContactsManager.h>
#import <SignalMessaging/OWSNavigationController.h>
2017-12-19 03:50:51 +01:00
#import <SignalMessaging/OWSPreferences.h>
#import <SignalMessaging/OWSProfileManager.h>
2017-11-30 16:02:04 +01:00
#import <SignalMessaging/SignalMessaging.h>
2017-12-19 03:50:51 +01:00
#import <SignalMessaging/VersionMigrations.h>
2020-06-05 02:38:44 +02:00
#import <SessionServiceKit/AppReadiness.h>
#import <SessionServiceKit/NSUserDefaults+OWS.h>
#import <SessionServiceKit/OWS2FAManager.h>
#import <SessionServiceKit/OWSBatchMessageProcessor.h>
#import <SessionServiceKit/OWSDisappearingMessagesJob.h>
#import <SessionServiceKit/OWSFailedAttachmentDownloadsJob.h>
#import <SessionServiceKit/OWSFailedMessagesJob.h>
#import <SessionServiceKit/OWSIncompleteCallsJob.h>
#import <SessionServiceKit/OWSMath.h>
#import <SessionServiceKit/OWSMessageManager.h>
#import <SessionServiceKit/OWSMessageSender.h>
#import <SessionServiceKit/OWSPrimaryStorage+Calling.h>
#import <SessionServiceKit/OWSReadReceiptManager.h>
#import <SessionServiceKit/SSKEnvironment.h>
2020-06-05 05:43:06 +02:00
#import <SessionServiceKit/SessionServiceKit-Swift.h>
2020-06-05 02:38:44 +02:00
#import <SessionServiceKit/TSAccountManager.h>
#import <SessionServiceKit/TSDatabaseView.h>
#import <SessionServiceKit/TSPreKeyManager.h>
#import <SessionServiceKit/TSSocketManager.h>
2018-01-24 23:11:18 +01:00
#import <YapDatabase/YapDatabaseCryptoUtils.h>
2018-10-25 16:18:09 +02:00
#import <sys/utsname.h>
@import WebRTC;
@import Intents;
NSString *const AppDelegateStoryboardMain = @"Main";
static NSString *const kInitialViewControllerIdentifier = @"UserInitialViewController";
static NSString *const kURLSchemeSGNLKey = @"sgnl";
static NSString *const kURLHostVerifyPrefix = @"verify";
static NSTimeInterval launchStartedAt;
2019-09-20 07:53:24 +02:00
@interface AppDelegate () <UNUserNotificationCenterDelegate>
2014-05-06 19:41:08 +02:00
@property (nonatomic) BOOL hasInitialRootViewController;
@property (nonatomic) BOOL areVersionMigrationsComplete;
@property (nonatomic) BOOL didAppLaunchFail;
2019-08-29 07:21:45 +02:00
// Loki
@property (nonatomic) LKPoller *poller;
@property (nonatomic) LKClosedGroupPoller *closedGroupPoller;
2014-05-06 19:41:08 +02:00
@end
#pragma mark -
@implementation AppDelegate
2014-05-06 19:41:08 +02:00
@synthesize window = _window;
#pragma mark - Dependencies
- (OWSProfileManager *)profileManager
{
return [OWSProfileManager sharedManager];
}
- (OWSReadReceiptManager *)readReceiptManager
{
return [OWSReadReceiptManager sharedManager];
}
- (id<OWSUDManager>)udManager
{
OWSAssertDebug(SSKEnvironment.shared.udManager);
return SSKEnvironment.shared.udManager;
}
- (OWSPrimaryStorage *)primaryStorage
{
OWSAssertDebug(SSKEnvironment.shared.primaryStorage);
return SSKEnvironment.shared.primaryStorage;
}
- (PushRegistrationManager *)pushRegistrationManager
{
OWSAssertDebug(AppEnvironment.shared.pushRegistrationManager);
return AppEnvironment.shared.pushRegistrationManager;
}
- (TSAccountManager *)tsAccountManager
{
OWSAssertDebug(SSKEnvironment.shared.tsAccountManager);
return SSKEnvironment.shared.tsAccountManager;
}
- (OWSDisappearingMessagesJob *)disappearingMessagesJob
{
OWSAssertDebug(SSKEnvironment.shared.disappearingMessagesJob);
return SSKEnvironment.shared.disappearingMessagesJob;
}
- (TSSocketManager *)socketManager
{
OWSAssertDebug(SSKEnvironment.shared.socketManager);
return SSKEnvironment.shared.socketManager;
}
- (OWSMessageManager *)messageManager
{
OWSAssertDebug(SSKEnvironment.shared.messageManager);
return SSKEnvironment.shared.messageManager;
}
2018-10-25 19:02:30 +02:00
- (OWSWindowManager *)windowManager
{
return Environment.shared.windowManager;
}
2018-11-21 23:52:34 +01:00
- (OWSBackup *)backup
{
return AppEnvironment.shared.backup;
}
- (OWSNotificationPresenter *)notificationPresenter
{
return AppEnvironment.shared.notificationPresenter;
}
- (OWSUserNotificationActionHandler *)userNotificationActionHandler
{
return AppEnvironment.shared.userNotificationActionHandler;
}
- (OWSLegacyNotificationActionHandler *)legacyNotificationActionHandler
{
return AppEnvironment.shared.legacyNotificationActionHandler;
}
#pragma mark -
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[DDLog flushLog];
2019-06-11 08:22:35 +02:00
2020-06-30 08:05:35 +02:00
[self stopPoller];
[self stopClosedGroupPoller];
2020-06-30 08:05:35 +02:00
[self stopOpenGroupPollers];
2017-01-17 23:10:57 +01:00
}
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
{
OWSLogInfo(@"applicationDidReceiveMemoryWarning");
}
- (void)applicationWillTerminate:(UIApplication *)application
{
[DDLog flushLog];
2020-02-21 04:40:44 +01:00
2020-06-30 08:05:35 +02:00
[self stopPoller];
[self stopClosedGroupPoller];
2020-06-30 08:05:35 +02:00
[self stopOpenGroupPollers];
}
2014-05-06 19:41:08 +02:00
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
2017-11-29 17:01:30 +01:00
2020-07-22 09:48:12 +02:00
// This should be the first thing we do
2017-11-29 20:51:39 +01:00
SetCurrentAppContext([MainAppContext new]);
2017-11-29 17:01:30 +01:00
launchStartedAt = CACurrentMediaTime();
BOOL isLoggingEnabled;
#ifdef DEBUG
// Specified at Product -> Scheme -> Edit Scheme -> Test -> Arguments -> Environment to avoid things like
// the phone directory being looked up during tests.
isLoggingEnabled = TRUE;
[DebugLogger.sharedLogger enableTTYLogging];
#elif RELEASE
isLoggingEnabled = OWSPreferences.isLoggingEnabled;
#endif
if (isLoggingEnabled) {
[DebugLogger.sharedLogger enableFileLogging];
}
2018-08-03 01:21:01 +02:00
[Cryptography seedRandom];
// XXX - careful when moving this. It must happen before we initialize OWSPrimaryStorage.
[self verifyDBKeysAvailableBeforeBackgroundLaunch];
2017-11-28 19:46:26 +01:00
#if RELEASE
2017-11-28 22:30:08 +01:00
// ensureIsReadyForAppExtensions may have changed the state of the logging
2017-11-30 16:02:04 +01:00
// preference (due to [NSUserDefaults migrateToSharedUserDefaults]), so honor
// that change if necessary.
if (isLoggingEnabled && !OWSPreferences.isLoggingEnabled) {
2017-11-28 19:46:26 +01:00
[DebugLogger.sharedLogger disableFileLogging];
}
#endif
2018-08-02 21:18:40 +02:00
[AppVersion sharedInstance];
2017-09-14 21:24:31 +02:00
[self startupLogging];
// Prevent the device from sleeping during database view async registration
// (e.g. long database upgrades).
//
2017-12-19 04:56:02 +01:00
// This block will be cleared in storageIsReady.
[DeviceSleepManager.sharedInstance addBlockWithBlockObject:self];
2018-09-17 15:27:58 +02:00
[AppSetup
setupEnvironmentWithAppSpecificSingletonBlock:^{
2020-07-22 09:48:12 +02:00
// Create AppEnvironment
2018-10-15 21:51:43 +02:00
[AppEnvironment.shared setup];
2018-10-15 20:34:41 +02:00
[SignalApp.sharedApp setup];
}
migrationCompletion:^{
OWSAssertIsOnMainThread();
[self versionMigrationsDidComplete];
2017-12-07 19:53:13 +01:00
}];
2020-01-20 03:20:27 +01:00
[LKAppearanceUtilities switchToSessionAppearance];
2017-12-04 18:38:44 +01:00
if (CurrentAppContext().isRunningTests) {
return YES;
}
UIWindow *mainWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window = mainWindow;
CurrentAppContext().mainWindow = mainWindow;
// Show LoadingViewController until the async database view registrations are complete.
mainWindow.rootViewController = [LoadingViewController new];
[mainWindow makeKeyAndVisible];
if (@available(iOS 11, *)) {
// This must happen in appDidFinishLaunching or earlier to ensure we don't
// miss notifications.
// Setting the delegate also seems to prevent us from getting the legacy notification
// notification callbacks upon launch e.g. 'didReceiveLocalNotification'
UNUserNotificationCenter.currentNotificationCenter.delegate = self;
}
// Accept push notification when app is not open
NSDictionary *remoteNotif = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
2014-05-06 19:41:08 +02:00
if (remoteNotif) {
OWSLogInfo(@"Application was launched by tapping a push notification.");
[self application:application didReceiveRemoteNotification:remoteNotif];
2014-05-06 19:41:08 +02:00
}
2018-03-21 20:46:23 +01:00
[OWSScreenLockUI.sharedManager setupWithRootWindow:self.window];
2018-04-24 23:00:39 +02:00
[[OWSWindowManager sharedManager] setupWithRootWindow:self.window
screenBlockingWindow:OWSScreenLockUI.sharedManager.screenBlockingWindow];
[OWSScreenLockUI.sharedManager startObserving];
[[NSNotificationCenter defaultCenter] addObserver:self
2017-12-19 04:56:02 +01:00
selector:@selector(storageIsReady)
name:StorageIsReadyNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(registrationStateDidChange)
2017-12-04 18:38:44 +01:00
name:RegistrationStateDidChangeNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(registrationLockDidChange:)
name:NSNotificationName_2FAStateDidChange
object:nil];
2019-11-20 06:27:34 +01:00
// Loki - Observe data nuke request notifications
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(handleDataNukeRequested:) name:NSNotification.dataNukeRequested object:nil];
OWSLogInfo(@"application: didFinishLaunchingWithOptions completed.");
2017-07-21 17:49:38 +02:00
[OWSAnalytics appLaunchDidBegin];
2020-03-06 01:54:20 +01:00
2014-05-06 19:41:08 +02:00
return YES;
}
2017-12-05 17:35:43 +01:00
/**
* The user must unlock the device once after reboot before the database encryption key can be accessed.
*/
- (void)verifyDBKeysAvailableBeforeBackgroundLaunch
{
2020-07-22 09:48:12 +02:00
if ([UIApplication sharedApplication].applicationState != UIApplicationStateBackground) { return; }
2017-12-05 17:35:43 +01:00
2020-07-22 09:48:12 +02:00
if (!OWSPrimaryStorage.isDatabasePasswordAccessible) {
OWSLogInfo(@"Exiting because we are in the background and the database password is not accessible.");
UILocalNotification *notification = [UILocalNotification new];
NSString *messageFormat = NSLocalizedString(@"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT",
@"Lock screen notification text presented after user powers on their device without unlocking. Embeds "
@"{{device model}} (either 'iPad' or 'iPhone')");
notification.alertBody = [NSString stringWithFormat:messageFormat, UIDevice.currentDevice.localizedModel];
// Make sure we clear any existing notifications so that they don't start stacking up
// if the user receives multiple pushes.
[UIApplication.sharedApplication cancelAllLocalNotifications];
[UIApplication.sharedApplication setApplicationIconBadgeNumber:0];
[UIApplication.sharedApplication scheduleLocalNotification:notification];
[UIApplication.sharedApplication setApplicationIconBadgeNumber:1];
2018-11-19 18:20:19 +01:00
[DDLog flushLog];
exit(0);
2017-12-05 17:35:43 +01:00
}
}
- (void)showLaunchFailureUI:(NSError *)error
{
// Disable normal functioning of app.
self.didAppLaunchFail = YES;
// We perform a subset of the [application:didFinishLaunchingWithOptions:].
2018-08-02 21:18:40 +02:00
[AppVersion sharedInstance];
[self startupLogging];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Show the launch screen
self.window.rootViewController =
[[UIStoryboard storyboardWithName:@"Launch Screen" bundle:nil] instantiateInitialViewController];
[self.window makeKeyAndVisible];
UIAlertController *alert =
[UIAlertController alertControllerWithTitle:NSLocalizedString(@"APP_LAUNCH_FAILURE_ALERT_TITLE",
@"Title for the 'app launch failed' alert.")
message:NSLocalizedString(@"APP_LAUNCH_FAILURE_ALERT_MESSAGE",
@"Message for the 'app launch failed' alert.")
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"SETTINGS_ADVANCED_SUBMIT_DEBUGLOG", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
[Pastelog submitLogsWithCompletion:^{
OWSFail(@"Exiting after sharing debug logs.");
}];
}]];
UIViewController *fromViewController = [[UIApplication sharedApplication] frontmostViewController];
[fromViewController presentAlert:alert];
2017-11-28 19:46:26 +01:00
}
2017-09-14 21:24:31 +02:00
- (void)startupLogging
{
OWSLogInfo(@"iOS Version: %@", [UIDevice currentDevice].systemVersion);
2017-09-14 21:24:31 +02:00
NSString *localeIdentifier = [NSLocale.currentLocale objectForKey:NSLocaleIdentifier];
if (localeIdentifier.length > 0) {
OWSLogInfo(@"Locale Identifier: %@", localeIdentifier);
2017-09-14 21:24:31 +02:00
}
NSString *countryCode = [NSLocale.currentLocale objectForKey:NSLocaleCountryCode];
if (countryCode.length > 0) {
OWSLogInfo(@"Country Code: %@", countryCode);
2017-09-14 21:24:31 +02:00
}
NSString *languageCode = [NSLocale.currentLocale objectForKey:NSLocaleLanguageCode];
if (languageCode.length > 0) {
OWSLogInfo(@"Language Code: %@", languageCode);
2017-09-14 21:24:31 +02:00
}
2018-10-25 16:18:09 +02:00
struct utsname systemInfo;
uname(&systemInfo);
OWSLogInfo(@"Device Model: %@ (%@)",
UIDevice.currentDevice.model,
[NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]);
2018-10-25 22:08:02 +02:00
NSDictionary<NSString *, NSString *> *buildDetails =
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"BuildDetails"];
OWSLogInfo(@"WebRTC Commit: %@", buildDetails[@"WebRTCCommit"]);
OWSLogInfo(@"Build XCode Version: %@", buildDetails[@"XCodeVersion"]);
OWSLogInfo(@"Build OS X Version: %@", buildDetails[@"OSXVersion"]);
OWSLogInfo(@"Build Cocoapods Version: %@", buildDetails[@"CocoapodsVersion"]);
OWSLogInfo(@"Build Carthage Version: %@", buildDetails[@"CarthageVersion"]);
OWSLogInfo(@"Build Date/Time: %@", buildDetails[@"DateTime"]);
2017-09-14 21:24:31 +02:00
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"App launch failed");
return;
}
2020-04-17 06:32:26 +02:00
[self.pushRegistrationManager didReceiveVanillaPushToken:deviceToken];
OWSLogInfo(@"Registering for push notifications with token: %@.", deviceToken);
2020-04-14 08:32:10 +02:00
BOOL isUsingFullAPNs = [NSUserDefaults.standardUserDefaults boolForKey:@"isUsingFullAPNs"];
if (isUsingFullAPNs) {
2020-04-17 05:55:24 +02:00
__unused AnyPromise *promise = [LKPushNotificationManager registerWithToken:deviceToken hexEncodedPublicKey:self.tsAccountManager.localNumber isForcedUpdate:NO];
2020-04-16 05:58:43 +02:00
} else {
2020-04-17 05:55:24 +02:00
__unused AnyPromise *promise = [LKPushNotificationManager registerWithToken:deviceToken isForcedUpdate:NO];
}
2014-05-06 19:41:08 +02:00
}
- (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];
2014-05-06 19:41:08 +02:00
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"App launch failed");
return;
}
2017-12-04 18:38:44 +01:00
if (CurrentAppContext().isRunningTests) {
2015-12-24 18:20:04 +01:00
return;
}
2017-02-10 22:24:48 +01:00
[self ensureRootViewController];
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
[self handleActivation];
}];
2018-05-10 19:55:31 +02:00
// Clear all notifications whenever we become active.
// When opening the app from a notification,
// AppDelegate.didReceiveLocalNotification will always
// be called _before_ we become active.
2018-05-11 21:45:38 +02:00
[self clearAllNotificationsAndRestoreBadgeCount];
2018-05-10 19:55:31 +02:00
2019-03-26 22:45:12 +01:00
// On every activation, clear old temp directories.
ClearOldTemporaryDirectories();
}
- (void)enableBackgroundRefreshIfNecessary
{
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
[UIApplication.sharedApplication setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
}];
}
- (void)handleActivation
{
OWSAssertIsOnMainThread();
// Always check prekeys after app launches, and sometimes check on app activation.
[TSPreKeyManager checkPreKeysIfNecessary];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RTCInitializeSSL();
if ([self.tsAccountManager isRegistered]) {
// At this point, potentially lengthy DB locking migrations could be running.
// Avoid blocking app launch by putting all further possible DB access in async block
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OWSLogInfo(@"Running post launch block for registered user: %@.", [self.tsAccountManager localNumber]);
2017-11-08 19:03:51 +01:00
// Clean up any messages that expired since last launch immediately
// and continue cleaning in the background.
[self.disappearingMessagesJob startIfNecessary];
[self enableBackgroundRefreshIfNecessary];
// Mark all "attempting out" messages as "unsent", i.e. any messages that were not successfully
// sent before the app exited should be marked as failures.
[[[OWSFailedMessagesJob alloc] initWithPrimaryStorage:self.primaryStorage] run];
// Mark all "incomplete" calls as missed, e.g. any incoming or outgoing calls that were not
// connected, failed or hung up before the app existed should be marked as missed.
[[[OWSIncompleteCallsJob alloc] initWithPrimaryStorage:self.primaryStorage] run];
[[[OWSFailedAttachmentDownloadsJob alloc] initWithPrimaryStorage:self.primaryStorage] run];
});
} else {
OWSLogInfo(@"Running post launch block for unregistered user.");
// Unregistered user should have no unread messages. e.g. if you delete your account.
[AppEnvironment.shared.notificationPresenter clearAllNotifications];
[self.socketManager requestSocketOpen];
UITapGestureRecognizer *gesture =
[[UITapGestureRecognizer alloc] initWithTarget:[Pastelog class] action:@selector(submitLogs)];
gesture.numberOfTapsRequired = 8;
[self.window addGestureRecognizer:gesture];
}
}); // end dispatchOnce for first time we become active
// Every time we become active...
if ([self.tsAccountManager isRegistered]) {
// At this point, potentially lengthy DB locking migrations could be running.
// Avoid blocking app launch by putting all further possible DB access in async block
dispatch_async(dispatch_get_main_queue(), ^{
[self.socketManager requestSocketOpen];
[Environment.shared.contactsManager fetchSystemContactsOnceIfAlreadyAuthorized];
2019-09-26 06:43:37 +02:00
2020-02-20 06:59:05 +01:00
NSString *userHexEncodedPublicKey = self.tsAccountManager.localNumber;
2020-03-25 00:27:43 +01:00
[self startPollerIfNeeded];
[self startClosedGroupPollerIfNeeded];
2020-02-21 04:40:44 +01:00
[self startOpenGroupPollersIfNeeded];
2019-09-26 06:43:37 +02:00
2020-02-20 06:59:05 +01:00
// Loki: Update profile picture if needed
NSUserDefaults *userDefaults = NSUserDefaults.standardUserDefaults;
NSDate *now = [NSDate new];
NSDate *lastProfilePictureUpload = (NSDate *)[userDefaults objectForKey:@"lastProfilePictureUpload"];
2020-07-22 09:48:12 +02:00
if (lastProfilePictureUpload != nil && [now timeIntervalSinceDate:lastProfilePictureUpload] > 4 * 24 * 60 * 60) {
2020-02-20 06:59:05 +01:00
OWSProfileManager *profileManager = OWSProfileManager.sharedManager;
2020-04-20 08:53:40 +02:00
NSString *displayName = [profileManager profileNameForRecipientWithID:userHexEncodedPublicKey];
2020-02-20 06:59:05 +01:00
UIImage *profilePicture = [profileManager profileAvatarForRecipientId:userHexEncodedPublicKey];
[profileManager updateLocalProfileName:displayName avatarImage:profilePicture success:^{
2020-02-21 04:07:28 +01:00
// Do nothing; the user defaults flag is updated in LokiFileServerAPI
2020-02-20 06:59:05 +01:00
} failure:^(NSError *error) {
// Do nothing
} requiresSync:YES];
}
if (![UIApplication sharedApplication].isRegisteredForRemoteNotifications) {
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
// usually sufficient, but e.g. on iOS11, users who have disabled "Allow Notifications" and disabled
// "Background App Refresh" will not be able to obtain an APN token. Enabling those settings does not
// restart the app, so we check every activation for users who haven't yet registered.
2019-05-08 07:51:08 +02:00
__unused AnyPromise *promise =
[OWSSyncPushTokensJob runWithAccountManager:AppEnvironment.shared.accountManager
preferences:Environment.shared.preferences];
}
});
}
}
- (void)applicationWillResignActive:(UIApplication *)application
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"App launch failed");
return;
}
[self clearAllNotificationsAndRestoreBadgeCount];
[DDLog flushLog];
}
2018-05-11 21:45:38 +02:00
- (void)clearAllNotificationsAndRestoreBadgeCount
{
OWSAssertIsOnMainThread();
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
[AppEnvironment.shared.notificationPresenter clearAllNotifications];
2018-05-11 21:45:38 +02:00
[OWSMessageUtils.sharedManager updateApplicationBadgeCount];
}];
}
2015-10-31 23:13:28 +01:00
- (void)application:(UIApplication *)application
performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem
completionHandler:(void (^)(BOOL succeeded))completionHandler {
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"App launch failed");
2018-11-21 23:52:34 +01:00
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];
2018-02-13 04:41:52 +01:00
completionHandler(YES);
}];
2015-10-31 23:13:28 +01:00
}
2018-10-25 19:02:30 +02:00
#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 {
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread();
2017-11-06 18:37:15 +01:00
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
2018-07-18 04:20:31 +02:00
completionHandler:(void (^)())completionHandler
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"App launch failed");
2018-11-21 23:52:34 +01:00
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
2018-05-25 23:17:15 +02:00
completionHandler:(void (^)())completionHandler
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"App launch failed");
2018-11-21 23:52:34 +01:00
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];
}];
iOS 9 Support - Fixing size classes rendering bugs. - Supporting native iOS San Francisco font. - Quick Reply - Settings now slide to the left as suggested in original designed opposed to modal. - Simplification of restraints on many screens. - Full-API compatiblity with iOS 9 and iOS 8 legacy support. - Customized AddressBook Permission prompt when restrictions are enabled. If user installed Signal previously and already approved access to Contacts, don't bugg him again. - Fixes crash in migration for users who installed Signal <2.1.3 but hadn't signed up yet. - Xcode 7 / iOS 9 Travis Support - Bitcode Support is disabled until it is better understood how exactly optimizations are performed. In a first time, we will split out the crypto code into a separate binary to make it easier to optimize the non-sensitive code. Blog post with more details coming. - Partial ATS support. We are running our own Certificate Authority at Open Whisper Systems. Signal is doing certificate pinning to verify that certificates were signed by our own CA. Unfortunately Apple's App Transport Security requires to hand over chain verification to their framework with no control over the trust store. We have filed a radar to get ATS features with pinned certificates. In the meanwhile, ATS is disabled on our domain. We also followed Amazon's recommendations for our S3 domain we use to upload/download attachments. (#891) - Implement a unified `AFSecurityOWSPolicy` pinning strategy accross libraries (AFNetworking RedPhone/TextSecure & SocketRocket).
2015-09-01 19:22:08 +02:00
}
- (void)application:(UIApplication *)application
performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
{
2019-06-17 06:53:20 +02:00
NSLog(@"[Loki] Performing background fetch.");
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
2020-01-22 05:32:23 +01:00
NSMutableArray *promises = [NSMutableArray new];
__block AnyPromise *fetchMessagesPromise = [AppEnvironment.shared.messageFetcherJob run].then(^{
fetchMessagesPromise = nil;
2019-06-17 06:53:20 +02:00
}).catch(^{
2020-01-22 05:32:23 +01:00
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) {
2019-06-17 06:53:20 +02:00
completionHandler(UIBackgroundFetchResultFailed);
});
}];
}
- (void)versionMigrationsDidComplete
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"versionMigrationsDidComplete");
self.areVersionMigrationsComplete = YES;
[self checkIfAppIsReady];
}
2017-12-19 04:56:02 +01:00
- (void)storageIsReady
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"storageIsReady");
[self checkIfAppIsReady];
}
- (void)checkIfAppIsReady
{
OWSAssertIsOnMainThread();
2020-07-22 09:48:12 +02:00
// App isn't ready until storage is ready AND all version migrations are complete
if (!self.areVersionMigrationsComplete) {
return;
}
if (![OWSStorage isStorageReady]) {
return;
}
if ([AppReadiness isAppReady]) {
2020-07-22 09:48:12 +02:00
// Only mark the app as ready once
return;
}
2018-02-12 16:00:20 +01:00
// TODO: Once "app ready" logic is moved into AppSetup, move this line there.
[self.profileManager ensureLocalProfileCached];
2018-01-29 19:43:37 +01:00
// Note that this does much more than set a flag;
// it will also run all deferred blocks.
[AppReadiness setAppIsReady];
2020-07-22 09:48:12 +02:00
if (CurrentAppContext().isRunningTests) { return; }
2018-09-28 16:56:53 +02:00
if ([self.tsAccountManager isRegistered]) {
// This should happen at any launch, background or foreground
2019-05-08 07:51:08 +02:00
__unused AnyPromise *pushTokenpromise =
[OWSSyncPushTokensJob runWithAccountManager:AppEnvironment.shared.accountManager
preferences:Environment.shared.preferences];
2017-09-14 21:30:22 +02:00
}
[DeviceSleepManager.sharedInstance removeBlockWithBlockObject:self];
2018-08-02 21:18:40 +02:00
[AppVersion.sharedInstance mainAppLaunchDidComplete];
[Environment.shared.audioSession setup];
2018-10-22 17:42:53 +02:00
[SSKEnvironment.shared.reachabilityManager setup];
2018-08-31 19:44:13 +02:00
if (!Environment.shared.preferences.hasGeneratedThumbnails) {
[self.primaryStorage.newDatabaseConnection
asyncReadWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[TSAttachmentStream enumerateCollectionObjectsUsingBlock:^(id _Nonnull obj, BOOL *_Nonnull stop){
// no-op. It's sufficient to initWithCoder: each object.
}];
}
completionBlock:^{
2018-08-31 19:44:13 +02:00
[Environment.shared.preferences setHasGeneratedThumbnails:YES];
}];
}
2018-01-29 19:54:25 +01:00
#ifdef DEBUG
// A bug in orphan cleanup could be disastrous so let's only
// run it in DEBUG builds for a few releases.
//
// TODO: Release to production once we have analytics.
// TODO: Orphan cleanup is somewhat expensive - not least in doing a bunch
2020-07-22 09:48:12 +02:00
// TODO: of disk access. We might want to only run it "once per version"
// TODO: or something like that in production.
2018-08-02 21:18:40 +02:00
[OWSOrphanDataCleaner auditOnLaunchIfNecessary];
2018-01-29 19:54:25 +01:00
#endif
[self.profileManager fetchLocalUsersProfile];
[self.readReceiptManager prepareCachedValues];
2017-12-19 03:34:22 +01:00
// Disable the SAE until the main app has successfully completed launch process
// at least once in the post-SAE world.
[OWSPreferences setIsReadyForAppExtensions];
2017-12-19 04:56:02 +01:00
[self ensureRootViewController];
2018-03-06 16:10:22 +01:00
[self.messageManager startObserving];
[self.udManager setup];
2018-12-13 17:40:52 +01:00
[self preheatDatabaseViews];
[self.primaryStorage touchDbAsync];
2019-01-29 22:32:30 +01:00
// Every time the user upgrades to a new version:
//
// * Update account attributes.
// * Sync configuration.
if ([self.tsAccountManager isRegistered]) {
AppVersion *appVersion = AppVersion.sharedInstance;
if (appVersion.lastAppVersion.length > 0
&& ![appVersion.lastAppVersion isEqualToString:appVersion.currentAppVersion]) {
[[self.tsAccountManager updateAccountAttributes] retainUntilComplete];
2019-01-29 22:32:30 +01:00
[SSKEnvironment.shared.syncManager sendConfigurationSyncMessage];
}
}
2018-12-13 17:40:52 +01:00
}
- (void)preheatDatabaseViews
{
[self.primaryStorage.uiDatabaseConnection asyncReadWithBlock:^(YapDatabaseReadTransaction *transaction) {
for (NSString *viewName in @[
TSThreadDatabaseViewExtensionName,
TSMessageDatabaseViewExtensionName,
TSThreadOutgoingMessageDatabaseViewExtensionName,
2018-12-13 18:27:38 +01:00
TSUnreadDatabaseViewExtensionName,
TSUnseenDatabaseViewExtensionName,
TSThreadSpecialMessagesDatabaseViewExtensionName,
2018-12-13 17:40:52 +01:00
]) {
YapDatabaseViewTransaction *databaseView = [transaction ext:viewName];
OWSAssertDebug([databaseView isKindOfClass:[YapDatabaseViewTransaction class]]);
}
}];
}
- (void)registrationStateDidChange
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread();
[self enableBackgroundRefreshIfNecessary];
if ([self.tsAccountManager isRegistered]) {
// Start running the disappearing messages job in case the newly registered user
// enables this feature
[self.disappearingMessagesJob startIfNecessary];
[self.profileManager ensureLocalProfileCached];
// For non-legacy users, read receipts are on by default.
[self.readReceiptManager setAreReadReceiptsEnabled:YES];
2020-07-22 09:48:12 +02:00
2020-03-25 00:27:43 +01:00
[self startPollerIfNeeded];
[self startClosedGroupPollerIfNeeded];
2020-02-21 04:40:44 +01:00
[self startOpenGroupPollersIfNeeded];
}
}
- (void)registrationLockDidChange:(NSNotification *)notification
{
[self enableBackgroundRefreshIfNecessary];
}
- (void)ensureRootViewController
{
OWSAssertIsOnMainThread();
2020-07-22 09:48:12 +02:00
if (!AppReadiness.isAppReady || self.hasInitialRootViewController) { return; }
self.hasInitialRootViewController = YES;
2018-11-21 23:52:34 +01:00
UIViewController *rootViewController;
2018-11-22 01:39:40 +01:00
BOOL navigationBarHidden = NO;
if ([self.tsAccountManager isRegistered]) {
2018-11-21 23:52:34 +01:00
if (self.backup.hasPendingRestoreDecision) {
rootViewController = [BackupRestoreViewController new];
} else {
2019-11-28 06:42:07 +01:00
rootViewController = [HomeVC new];
2018-11-21 23:52:34 +01:00
}
} else {
2020-07-23 02:01:49 +02:00
rootViewController = [LandingVC new];
2019-12-06 04:42:43 +01:00
navigationBarHidden = NO;
}
2018-11-21 23:52:34 +01:00
OWSAssertDebug(rootViewController);
OWSNavigationController *navigationController =
[[OWSNavigationController alloc] initWithRootViewController:rootViewController];
2018-11-22 01:39:40 +01:00
navigationController.navigationBarHidden = navigationBarHidden;
2018-11-21 23:52:34 +01:00
self.window.rootViewController = navigationController;
2019-01-16 21:24:19 +01:00
[UIViewController attemptRotationToDeviceOrientation];
}
#pragma mark - status bar touches
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
CGPoint location = [[[event allTouches] anyObject] locationInView:[self window]];
CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
if (CGRectContainsPoint(statusBarFrame, location)) {
[[NSNotificationCenter defaultCenter] postNotificationName:TappedStatusBarNotification object:nil];
}
}
#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)
{
if (notification.request.content.userInfo[@"remote"]) {
2020-03-27 05:13:24 +01:00
OWSLogInfo(@"[Loki] Ignoring remote notifications while the app is in the foreground.");
return;
}
[AppReadiness runNowOrWhenAppDidBecomeReady:^() {
2019-01-30 23:27:53 +01:00
// We need to respect the in-app notification sound preference. This method, which is called
// for modern UNUserNotification users, could be a place to do that, but since we'd still
// 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
// honor notification sound preferences for both modern and legacy users.
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
{
[AppReadiness runNowOrWhenAppDidBecomeReady:^() {
[self.userNotificationActionHandler 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
{
2020-07-22 09:48:12 +02:00
}
#pragma mark - Loki
2020-03-25 00:27:43 +01:00
- (void)startPollerIfNeeded
2019-08-29 07:21:45 +02:00
{
if (self.poller == nil) {
NSString *userPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
2020-07-14 03:27:16 +02:00
if (userPublicKey != nil) {
self.poller = [[LKPoller alloc] init];
}
}
[self.poller startIfNeeded];
2019-08-29 07:21:45 +02:00
}
2020-06-30 08:05:35 +02:00
- (void)stopPoller { [self.poller stop]; }
- (void)startClosedGroupPollerIfNeeded
2019-08-29 07:21:45 +02:00
{
if (self.closedGroupPoller == nil) {
NSString *userPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
2020-07-14 03:27:16 +02:00
if (userPublicKey != nil) {
self.closedGroupPoller = [[LKClosedGroupPoller alloc] init];
}
}
[self.closedGroupPoller startIfNeeded];
}
- (void)stopClosedGroupPoller { [self.closedGroupPoller stop]; }
2020-02-21 04:40:44 +01:00
- (void)startOpenGroupPollersIfNeeded
{
[LKPublicChatManager.shared startPollersIfNeeded];
[SSKEnvironment.shared.attachmentDownloads continueDownloadIfPossible];
}
- (void)stopOpenGroupPollers { [LKPublicChatManager.shared stopPollers]; }
2020-02-21 04:40:44 +01:00
2019-11-20 06:27:34 +01:00
- (void)handleDataNukeRequested:(NSNotification *)notification {
[ThreadUtil deleteAllContent];
2019-11-21 05:40:30 +01:00
[SSKEnvironment.shared.messageSenderJobQueue clearAllJobs];
2019-11-20 06:27:34 +01:00
[SSKEnvironment.shared.identityManager clearIdentityKey];
[LKSnodeAPI clearSnodePool];
2020-06-30 08:05:35 +02:00
[self stopPoller];
[self stopClosedGroupPoller];
2020-06-30 08:05:35 +02:00
[self stopOpenGroupPollers];
2019-11-21 00:56:27 +01:00
[LKPublicChatManager.shared stopPollers];
2019-12-03 00:28:09 +01:00
bool wasUnlinked = [NSUserDefaults.standardUserDefaults boolForKey:@"wasUnlinked"];
[SignalApp resetAppData:^{
// Resetting the data clears the old user defaults. We need to restore the unlink default.
[NSUserDefaults.standardUserDefaults setBool:wasUnlinked forKey:@"wasUnlinked"];
}];
2019-11-20 06:27:34 +01:00
}
2014-05-06 19:41:08 +02:00
@end