mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			827 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Objective-C
		
	
			
		
		
	
	
			827 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Objective-C
		
	
//
 | 
						|
//  Copyright (c) 2019 Open Whisper Systems. All rights reserved.
 | 
						|
//
 | 
						|
 | 
						|
#import "AppDelegate.h"
 | 
						|
#import "DebugLogger.h"
 | 
						|
#import "MainAppContext.h"
 | 
						|
#import "OWSBackup.h"
 | 
						|
#import "OWSOrphanDataCleaner.h"
 | 
						|
#import "OWSScreenLockUI.h"
 | 
						|
#import "Session-Swift.h"
 | 
						|
#import "SignalApp.h"
 | 
						|
#import <PromiseKit/AnyPromise.h>
 | 
						|
#import <SignalCoreKit/iOSVersions.h>
 | 
						|
#import <SignalUtilitiesKit/AppSetup.h>
 | 
						|
#import <SessionMessagingKit/Environment.h>
 | 
						|
#import <SignalUtilitiesKit/OWSNavigationController.h>
 | 
						|
#import <SessionMessagingKit/OWSPreferences.h>
 | 
						|
#import <SignalUtilitiesKit/OWSProfileManager.h>
 | 
						|
#import <SignalUtilitiesKit/VersionMigrations.h>
 | 
						|
#import <SessionMessagingKit/AppReadiness.h>
 | 
						|
#import <SessionUtilitiesKit/NSUserDefaults+OWS.h>
 | 
						|
#import <SessionMessagingKit/OWSDisappearingMessagesJob.h>
 | 
						|
#import <SignalUtilitiesKit/OWSFailedAttachmentDownloadsJob.h>
 | 
						|
#import <SignalUtilitiesKit/OWSFailedMessagesJob.h>
 | 
						|
#import <SessionUtilitiesKit/OWSMath.h>
 | 
						|
#import <SessionMessagingKit/OWSReadReceiptManager.h>
 | 
						|
#import <SessionMessagingKit/SSKEnvironment.h>
 | 
						|
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
 | 
						|
#import <SessionMessagingKit/TSAccountManager.h>
 | 
						|
#import <SessionMessagingKit/TSDatabaseView.h>
 | 
						|
#import <YapDatabase/YapDatabaseCryptoUtils.h>
 | 
						|
#import <sys/utsname.h>
 | 
						|
 | 
						|
@import Intents;
 | 
						|
 | 
						|
NSString *const AppDelegateStoryboardMain = @"Main";
 | 
						|
 | 
						|
static NSString *const kInitialViewControllerIdentifier = @"UserInitialViewController";
 | 
						|
static NSString *const kURLSchemeSGNLKey = @"sgnl";
 | 
						|
static NSString *const kURLHostVerifyPrefix = @"verify";
 | 
						|
 | 
						|
static NSTimeInterval launchStartedAt;
 | 
						|
 | 
						|
@interface AppDelegate () <UNUserNotificationCenterDelegate, LKAppModeManagerDelegate>
 | 
						|
 | 
						|
@property (nonatomic) BOOL hasInitialRootViewController;
 | 
						|
@property (nonatomic) BOOL areVersionMigrationsComplete;
 | 
						|
@property (nonatomic) BOOL didAppLaunchFail;
 | 
						|
@property (nonatomic) LKPoller *poller;
 | 
						|
 | 
						|
@end
 | 
						|
 | 
						|
#pragma mark -
 | 
						|
 | 
						|
@implementation AppDelegate
 | 
						|
 | 
						|
@synthesize window = _window;
 | 
						|
 | 
						|
#pragma mark - Dependencies
 | 
						|
 | 
						|
- (OWSProfileManager *)profileManager
 | 
						|
{
 | 
						|
    return [OWSProfileManager sharedManager];
 | 
						|
}
 | 
						|
 | 
						|
- (OWSReadReceiptManager *)readReceiptManager
 | 
						|
{
 | 
						|
    return [OWSReadReceiptManager sharedManager];
 | 
						|
}
 | 
						|
 | 
						|
- (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;
 | 
						|
}
 | 
						|
 | 
						|
- (OWSWindowManager *)windowManager
 | 
						|
{
 | 
						|
    return Environment.shared.windowManager;
 | 
						|
}
 | 
						|
 | 
						|
- (OWSBackup *)backup
 | 
						|
{
 | 
						|
    return AppEnvironment.shared.backup;
 | 
						|
}
 | 
						|
 | 
						|
- (OWSNotificationPresenter *)notificationPresenter
 | 
						|
{
 | 
						|
    return AppEnvironment.shared.notificationPresenter;
 | 
						|
}
 | 
						|
 | 
						|
- (OWSUserNotificationActionHandler *)userNotificationActionHandler
 | 
						|
{
 | 
						|
    return AppEnvironment.shared.userNotificationActionHandler;
 | 
						|
}
 | 
						|
 | 
						|
#pragma mark - Lifecycle
 | 
						|
 | 
						|
- (void)applicationDidEnterBackground:(UIApplication *)application
 | 
						|
{
 | 
						|
    [DDLog flushLog];
 | 
						|
 | 
						|
    [self stopPoller];
 | 
						|
    [self stopClosedGroupPoller];
 | 
						|
    [self stopOpenGroupPollers];
 | 
						|
}
 | 
						|
 | 
						|
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
 | 
						|
{
 | 
						|
    OWSLogInfo(@"applicationDidReceiveMemoryWarning");
 | 
						|
}
 | 
						|
 | 
						|
- (void)applicationWillTerminate:(UIApplication *)application
 | 
						|
{
 | 
						|
    [DDLog flushLog];
 | 
						|
 | 
						|
    [self stopPoller];
 | 
						|
    [self stopClosedGroupPoller];
 | 
						|
    [self stopOpenGroupPollers];
 | 
						|
}
 | 
						|
 | 
						|
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
 | 
						|
 | 
						|
    // This should be the first thing we do
 | 
						|
    SetCurrentAppContext([MainAppContext new]);
 | 
						|
 | 
						|
    launchStartedAt = CACurrentMediaTime();
 | 
						|
 | 
						|
    [LKAppModeManager configureWithDelegate:self];
 | 
						|
 | 
						|
    // OWSLinkPreview is now in SessionMessagingKit, so to still be able to deserialize them we
 | 
						|
    // need to tell NSKeyedUnarchiver about the changes.
 | 
						|
    [NSKeyedUnarchiver setClass:OWSLinkPreview.class forClassName:@"SessionServiceKit.OWSLinkPreview"];
 | 
						|
 | 
						|
    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];
 | 
						|
    }
 | 
						|
 | 
						|
    [Cryptography seedRandom];
 | 
						|
 | 
						|
    // XXX - careful when moving this. It must happen before we initialize OWSPrimaryStorage.
 | 
						|
    [self verifyDBKeysAvailableBeforeBackgroundLaunch];
 | 
						|
 | 
						|
#if RELEASE
 | 
						|
    // ensureIsReadyForAppExtensions may have changed the state of the logging
 | 
						|
    // preference (due to [NSUserDefaults migrateToSharedUserDefaults]), so honor
 | 
						|
    // that change if necessary.
 | 
						|
    if (isLoggingEnabled && !OWSPreferences.isLoggingEnabled) {
 | 
						|
        [DebugLogger.sharedLogger disableFileLogging];
 | 
						|
    }
 | 
						|
#endif
 | 
						|
 | 
						|
    [AppVersion sharedInstance];
 | 
						|
 | 
						|
    // Prevent the device from sleeping during database view async registration
 | 
						|
    // (e.g. long database upgrades).
 | 
						|
    //
 | 
						|
    // This block will be cleared in storageIsReady.
 | 
						|
    [DeviceSleepManager.sharedInstance addBlockWithBlockObject:self];
 | 
						|
 | 
						|
    [AppSetup
 | 
						|
        setupEnvironmentWithAppSpecificSingletonBlock:^{
 | 
						|
            // Create AppEnvironment
 | 
						|
            [AppEnvironment.shared setup];
 | 
						|
            [SignalApp.sharedApp setup];
 | 
						|
        }
 | 
						|
        migrationCompletion:^{
 | 
						|
            OWSAssertIsOnMainThread();
 | 
						|
 | 
						|
            [self versionMigrationsDidComplete];
 | 
						|
        }];
 | 
						|
 | 
						|
    [SNConfiguration performMainSetup];
 | 
						|
 | 
						|
    [SNAppearance switchToSessionAppearance];
 | 
						|
 | 
						|
    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];
 | 
						|
 | 
						|
    LKAppMode appMode = [NSUserDefaults.standardUserDefaults integerForKey:@"appMode"];
 | 
						|
    [self setCurrentAppMode:appMode];
 | 
						|
 | 
						|
    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;
 | 
						|
    }
 | 
						|
 | 
						|
    [OWSScreenLockUI.sharedManager setupWithRootWindow:self.window];
 | 
						|
    [[OWSWindowManager sharedManager] setupWithRootWindow:self.window
 | 
						|
                                     screenBlockingWindow:OWSScreenLockUI.sharedManager.screenBlockingWindow];
 | 
						|
    [OWSScreenLockUI.sharedManager startObserving];
 | 
						|
 | 
						|
    [[NSNotificationCenter defaultCenter] addObserver:self
 | 
						|
                                             selector:@selector(storageIsReady)
 | 
						|
                                                 name:StorageIsReadyNotification
 | 
						|
                                               object:nil];
 | 
						|
    [[NSNotificationCenter defaultCenter] addObserver:self
 | 
						|
                                             selector:@selector(registrationStateDidChange)
 | 
						|
                                                 name:RegistrationStateDidChangeNotification
 | 
						|
                                               object:nil];
 | 
						|
 | 
						|
    // Loki - Observe data nuke request notifications
 | 
						|
    [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(handleDataNukeRequested:) name:NSNotification.dataNukeRequested object:nil];
 | 
						|
    
 | 
						|
    OWSLogInfo(@"application: didFinishLaunchingWithOptions completed.");
 | 
						|
 | 
						|
    return YES;
 | 
						|
}
 | 
						|
 | 
						|
- (void)applicationDidBecomeActive:(UIApplication *)application {
 | 
						|
    OWSAssertIsOnMainThread();
 | 
						|
 | 
						|
    if (self.didAppLaunchFail) {
 | 
						|
        OWSFailDebug(@"App launch failed");
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (CurrentAppContext().isRunningTests) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    
 | 
						|
    NSUserDefaults *sharedUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.loki-project.loki-messenger"];
 | 
						|
    [sharedUserDefaults setBool:YES forKey:@"isMainAppActive"];
 | 
						|
    [sharedUserDefaults synchronize];
 | 
						|
 | 
						|
    [self ensureRootViewController];
 | 
						|
 | 
						|
    LKAppMode appMode = [NSUserDefaults.standardUserDefaults integerForKey:@"appMode"];
 | 
						|
    [self setCurrentAppMode:appMode];
 | 
						|
 | 
						|
    [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];
 | 
						|
    
 | 
						|
    NSUserDefaults *sharedUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.loki-project.loki-messenger"];
 | 
						|
    [sharedUserDefaults setBool:NO forKey:@"isMainAppActive"];
 | 
						|
    [sharedUserDefaults synchronize];
 | 
						|
 | 
						|
    [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
 | 
						|
{
 | 
						|
    [AppReadiness runNowOrWhenAppDidBecomeReady:^{
 | 
						|
        [LKBackgroundPoller pollWithCompletionHandler:completionHandler];
 | 
						|
    }];
 | 
						|
}
 | 
						|
 | 
						|
#pragma mark - App Readiness
 | 
						|
 | 
						|
/**
 | 
						|
 *  The user must unlock the device once after reboot before the database encryption key can be accessed.
 | 
						|
 */
 | 
						|
- (void)verifyDBKeysAvailableBeforeBackgroundLaunch
 | 
						|
{
 | 
						|
    if ([UIApplication sharedApplication].applicationState != UIApplicationStateBackground) { return; }
 | 
						|
 | 
						|
    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];
 | 
						|
 | 
						|
        [DDLog flushLog];
 | 
						|
        exit(0);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
- (void)enableBackgroundRefreshIfNecessary
 | 
						|
{
 | 
						|
    [AppReadiness runNowOrWhenAppDidBecomeReady:^{
 | 
						|
        [UIApplication.sharedApplication setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
 | 
						|
    }];
 | 
						|
}
 | 
						|
 | 
						|
- (void)handleActivation
 | 
						|
{
 | 
						|
    OWSAssertIsOnMainThread();
 | 
						|
 | 
						|
    static dispatch_once_t onceToken;
 | 
						|
    dispatch_once(&onceToken, ^{
 | 
						|
        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]);
 | 
						|
 | 
						|
                // 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];
 | 
						|
                [[[OWSFailedAttachmentDownloadsJob alloc] initWithPrimaryStorage:self.primaryStorage] run];
 | 
						|
            });
 | 
						|
        }
 | 
						|
    }); // 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(), ^{
 | 
						|
            NSString *userPublicKey = self.tsAccountManager.localNumber;
 | 
						|
 | 
						|
            // Update profile picture if needed
 | 
						|
            NSUserDefaults *userDefaults = NSUserDefaults.standardUserDefaults;
 | 
						|
            NSDate *now = [NSDate new];
 | 
						|
            NSDate *lastProfilePictureUpload = (NSDate *)[userDefaults objectForKey:@"lastProfilePictureUpload"];
 | 
						|
            if (lastProfilePictureUpload != nil && [now timeIntervalSinceDate:lastProfilePictureUpload] > 14 * 24 * 60 * 60) {
 | 
						|
                OWSProfileManager *profileManager = OWSProfileManager.sharedManager;
 | 
						|
                NSString *name = [[LKStorage.shared getUser] name];
 | 
						|
                UIImage *profilePicture = [profileManager profileAvatarForRecipientId:userPublicKey];
 | 
						|
                [profileManager updateLocalProfileName:name avatarImage:profilePicture success:^{
 | 
						|
                    // Do nothing; the user defaults flag is updated in LokiFileServerAPI
 | 
						|
                } failure:^(NSError *error) {
 | 
						|
                    // Do nothing
 | 
						|
                } requiresSync:YES];
 | 
						|
            }
 | 
						|
            
 | 
						|
            if (CurrentAppContext().isMainApp) {
 | 
						|
                [SNOpenGroupAPIV2 getDefaultRoomsIfNeeded];
 | 
						|
            }
 | 
						|
            
 | 
						|
            [[SNSnodeAPI getSnodePool] retainUntilComplete];
 | 
						|
            
 | 
						|
            [self startPollerIfNeeded];
 | 
						|
            [self startClosedGroupPoller];
 | 
						|
            [self startOpenGroupPollersIfNeeded];
 | 
						|
 | 
						|
            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.
 | 
						|
                __unused AnyPromise *promise =
 | 
						|
                    [OWSSyncPushTokensJob runWithAccountManager:AppEnvironment.shared.accountManager
 | 
						|
                                                    preferences:Environment.shared.preferences];
 | 
						|
            }
 | 
						|
            
 | 
						|
            if (CurrentAppContext().isMainApp) {
 | 
						|
                [SNJobQueue.shared resumePendingJobs];
 | 
						|
                [self syncConfigurationIfNeeded];
 | 
						|
            }
 | 
						|
        });
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
- (void)versionMigrationsDidComplete
 | 
						|
{
 | 
						|
    OWSAssertIsOnMainThread();
 | 
						|
 | 
						|
    self.areVersionMigrationsComplete = YES;
 | 
						|
 | 
						|
    [self checkIfAppIsReady];
 | 
						|
}
 | 
						|
 | 
						|
- (void)storageIsReady
 | 
						|
{
 | 
						|
    OWSAssertIsOnMainThread();
 | 
						|
 | 
						|
    [self checkIfAppIsReady];
 | 
						|
}
 | 
						|
 | 
						|
- (void)checkIfAppIsReady
 | 
						|
{
 | 
						|
    OWSAssertIsOnMainThread();
 | 
						|
 | 
						|
    // 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]) {
 | 
						|
        // Only mark the app as ready once
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    
 | 
						|
    [SNConfiguration performMainSetup];
 | 
						|
 | 
						|
    // TODO: Once "app ready" logic is moved into AppSetup, move this line there.
 | 
						|
    [self.profileManager ensureLocalProfileCached];
 | 
						|
 | 
						|
    // Note that this does much more than set a flag;
 | 
						|
    // it will also run all deferred blocks.
 | 
						|
    [AppReadiness setAppIsReady];
 | 
						|
 | 
						|
    if (CurrentAppContext().isRunningTests) { return; }
 | 
						|
 | 
						|
    if ([self.tsAccountManager isRegistered]) {
 | 
						|
 | 
						|
        // This should happen at any launch, background or foreground
 | 
						|
        __unused AnyPromise *pushTokenpromise =
 | 
						|
            [OWSSyncPushTokensJob runWithAccountManager:AppEnvironment.shared.accountManager
 | 
						|
                                            preferences:Environment.shared.preferences];
 | 
						|
    }
 | 
						|
 | 
						|
    [DeviceSleepManager.sharedInstance removeBlockWithBlockObject:self];
 | 
						|
 | 
						|
    [AppVersion.sharedInstance mainAppLaunchDidComplete];
 | 
						|
 | 
						|
    [Environment.shared.audioSession setup];
 | 
						|
 | 
						|
    [SSKEnvironment.shared.reachabilityManager setup];
 | 
						|
 | 
						|
    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:^{
 | 
						|
                [Environment.shared.preferences setHasGeneratedThumbnails:YES];
 | 
						|
            }];
 | 
						|
    }
 | 
						|
    
 | 
						|
    [self.readReceiptManager prepareCachedValues];
 | 
						|
 | 
						|
    // Disable the SAE until the main app has successfully completed launch process
 | 
						|
    // at least once in the post-SAE world.
 | 
						|
    [OWSPreferences setIsReadyForAppExtensions];
 | 
						|
 | 
						|
    [self ensureRootViewController];
 | 
						|
 | 
						|
    [self preheatDatabaseViews];
 | 
						|
 | 
						|
    [self.primaryStorage touchDbAsync];
 | 
						|
 | 
						|
    // 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];
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
- (void)preheatDatabaseViews
 | 
						|
{
 | 
						|
    [self.primaryStorage.uiDatabaseConnection asyncReadWithBlock:^(YapDatabaseReadTransaction *transaction) {
 | 
						|
        for (NSString *viewName in @[
 | 
						|
                 TSThreadDatabaseViewExtensionName,
 | 
						|
                 TSMessageDatabaseViewExtensionName,
 | 
						|
                 TSThreadOutgoingMessageDatabaseViewExtensionName,
 | 
						|
                 TSUnreadDatabaseViewExtensionName,
 | 
						|
                 TSUnseenDatabaseViewExtensionName,
 | 
						|
             ]) {
 | 
						|
            YapDatabaseViewTransaction *databaseView = [transaction ext:viewName];
 | 
						|
            OWSAssertDebug([databaseView isKindOfClass:[YapDatabaseViewTransaction class]]);
 | 
						|
        }
 | 
						|
    }];
 | 
						|
}
 | 
						|
 | 
						|
- (void)registrationStateDidChange
 | 
						|
{
 | 
						|
    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];
 | 
						|
 | 
						|
        [self startPollerIfNeeded];
 | 
						|
        [self startClosedGroupPoller];
 | 
						|
        [self startOpenGroupPollersIfNeeded];
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
- (void)registrationLockDidChange:(NSNotification *)notification
 | 
						|
{
 | 
						|
    [self enableBackgroundRefreshIfNecessary];
 | 
						|
}
 | 
						|
 | 
						|
- (void)ensureRootViewController
 | 
						|
{
 | 
						|
    OWSAssertIsOnMainThread();
 | 
						|
 | 
						|
    if (!AppReadiness.isAppReady || self.hasInitialRootViewController) { return; }
 | 
						|
    self.hasInitialRootViewController = YES;
 | 
						|
 | 
						|
    UIViewController *rootViewController;
 | 
						|
    BOOL navigationBarHidden = NO;
 | 
						|
    if ([self.tsAccountManager isRegistered]) {
 | 
						|
        if (self.backup.hasPendingRestoreDecision) {
 | 
						|
            rootViewController = [BackupRestoreViewController new];
 | 
						|
        } else {
 | 
						|
            rootViewController = [HomeVC new];
 | 
						|
        }
 | 
						|
    } else {
 | 
						|
        rootViewController = [LandingVC new];
 | 
						|
        navigationBarHidden = NO;
 | 
						|
    }
 | 
						|
    OWSAssertDebug(rootViewController);
 | 
						|
    OWSNavigationController *navigationController =
 | 
						|
        [[OWSNavigationController alloc] initWithRootViewController:rootViewController];
 | 
						|
    navigationController.navigationBarHidden = navigationBarHidden;
 | 
						|
    self.window.rootViewController = navigationController;
 | 
						|
 | 
						|
    [UIViewController attemptRotationToDeviceOrientation];
 | 
						|
}
 | 
						|
 | 
						|
#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 = [LKPushNotificationAPI registerWithToken:deviceToken hexEncodedPublicKey:self.tsAccountManager.localNumber isForcedUpdate:NO];
 | 
						|
    } else {
 | 
						|
        __unused AnyPromise *promise = [LKPushNotificationAPI unregisterToken:deviceToken];
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
- (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
 | 
						|
    [self.pushRegistrationManager didFailToReceiveVanillaPushTokenWithError:error];
 | 
						|
#endif
 | 
						|
}
 | 
						|
 | 
						|
- (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 createNewDM];
 | 
						|
        completionHandler(YES);
 | 
						|
    }];
 | 
						|
}
 | 
						|
 | 
						|
// 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"]) {
 | 
						|
        OWSLogInfo(@"[Loki] Ignoring remote notifications while the app is in the foreground.");
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    [AppReadiness runNowOrWhenAppDidBecomeReady:^() {
 | 
						|
        // 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
 | 
						|
{
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
#pragma mark - Polling
 | 
						|
 | 
						|
- (void)startPollerIfNeeded
 | 
						|
{
 | 
						|
    if (self.poller == nil) {
 | 
						|
        NSString *userPublicKey = [SNGeneralUtilities getUserPublicKey];
 | 
						|
        if (userPublicKey != nil) {
 | 
						|
            self.poller = [[LKPoller alloc] init];
 | 
						|
        }
 | 
						|
    }
 | 
						|
    [self.poller startIfNeeded];
 | 
						|
}
 | 
						|
 | 
						|
- (void)stopPoller { [self.poller stop]; }
 | 
						|
 | 
						|
- (void)startOpenGroupPollersIfNeeded
 | 
						|
{
 | 
						|
    [SNOpenGroupManagerV2.shared startPolling];
 | 
						|
}
 | 
						|
 | 
						|
- (void)stopOpenGroupPollers {
 | 
						|
    [SNOpenGroupManagerV2.shared stopPolling];
 | 
						|
}
 | 
						|
 | 
						|
# pragma mark - App Mode
 | 
						|
 | 
						|
- (LKAppMode)getCurrentAppMode
 | 
						|
{
 | 
						|
    return [NSUserDefaults.standardUserDefaults integerForKey:@"appMode"];
 | 
						|
}
 | 
						|
 | 
						|
- (void)setCurrentAppMode:(LKAppMode)appMode
 | 
						|
{
 | 
						|
    UIWindow *window = UIApplication.sharedApplication.keyWindow;
 | 
						|
    if (window == nil) { return; }
 | 
						|
    [NSUserDefaults.standardUserDefaults setInteger:appMode forKey:@"appMode"];
 | 
						|
    switch (appMode) {
 | 
						|
        case LKAppModeLight: {
 | 
						|
            if (@available(iOS 13.0, *)) {
 | 
						|
                window.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
 | 
						|
            }
 | 
						|
            window.backgroundColor = UIColor.whiteColor;
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        case LKAppModeDark: {
 | 
						|
            if (@available(iOS 13.0, *)) {
 | 
						|
                window.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
 | 
						|
            }
 | 
						|
            window.backgroundColor = UIColor.blackColor;
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    [NSNotificationCenter.defaultCenter postNotificationName:NSNotification.appModeChanged object:nil];
 | 
						|
}
 | 
						|
 | 
						|
# pragma mark - Other
 | 
						|
 | 
						|
- (void)handleDataNukeRequested:(NSNotification *)notification
 | 
						|
{
 | 
						|
    NSUserDefaults *userDefaults = NSUserDefaults.standardUserDefaults;
 | 
						|
    BOOL isUsingFullAPNs = [userDefaults boolForKey:@"isUsingFullAPNs"];
 | 
						|
    NSString *hexEncodedDeviceToken = [userDefaults stringForKey:@"deviceToken"];
 | 
						|
    if (isUsingFullAPNs && hexEncodedDeviceToken != nil) {
 | 
						|
        NSData *deviceToken = [NSData dataFromHexString:hexEncodedDeviceToken];
 | 
						|
        [[LKPushNotificationAPI unregisterToken:deviceToken] retainUntilComplete];
 | 
						|
    }
 | 
						|
    [ThreadUtil deleteAllContent];
 | 
						|
    [SSKEnvironment.shared.identityManager clearIdentityKey];
 | 
						|
    [SNSnodeAPI clearSnodePool];
 | 
						|
    [self stopPoller];
 | 
						|
    [self stopClosedGroupPoller];
 | 
						|
    [self stopOpenGroupPollers];
 | 
						|
    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"];
 | 
						|
    }];
 | 
						|
}
 | 
						|
 | 
						|
# pragma mark - App Link
 | 
						|
 | 
						|
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options
 | 
						|
{
 | 
						|
    NSURLComponents *components = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:true];
 | 
						|
    
 | 
						|
    // URL Scheme is sessionmessenger://DM?sessionID=1234
 | 
						|
    // We can later add more parameters like message etc.
 | 
						|
    NSString *intent = components.host;
 | 
						|
    if (intent != nil && [intent isEqualToString:@"DM"]) {
 | 
						|
        NSArray<NSURLQueryItem*> *params = [components queryItems];
 | 
						|
        NSPredicate *sessionIDPredicate = [NSPredicate predicateWithFormat:@"name == %@", @"sessionID"];
 | 
						|
        NSArray<NSURLQueryItem*> *matches = [params filteredArrayUsingPredicate:sessionIDPredicate];
 | 
						|
        if (matches.count > 0) {
 | 
						|
            NSString *sessionID = matches.firstObject.value;
 | 
						|
            [self createNewDMFromDeepLink:sessionID];
 | 
						|
            return YES;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return NO;
 | 
						|
}
 | 
						|
 | 
						|
- (void)createNewDMFromDeepLink:(NSString *)sessionID
 | 
						|
{
 | 
						|
    UIViewController *viewController = self.window.rootViewController;
 | 
						|
    if ([viewController class] == [OWSNavigationController class]) {
 | 
						|
        UIViewController *visibleVC = ((OWSNavigationController *)viewController).visibleViewController;
 | 
						|
        if ([visibleVC isKindOfClass:HomeVC.class]) {
 | 
						|
            HomeVC *homeVC = (HomeVC *)visibleVC;
 | 
						|
            [homeVC createNewDMFromDeepLink:sessionID];
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
@end
 |