session-ios/Signal/src/AppDelegate.m

1366 lines
53 KiB
Mathematica
Raw Normal View History

2017-01-17 22:01:19 +01:00
//
2018-01-09 16:12:14 +01:00
// Copyright (c) 2018 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 "AppStoreRating.h"
#import "AppUpdateNag.h"
#import "CodeVerificationViewController.h"
#import "DebugLogger.h"
2017-11-29 20:51:39 +01:00
#import "MainAppContext.h"
#import "NotificationsManager.h"
#import "OWS2FASettingsViewController.h"
2018-03-06 16:10:22 +01:00
#import "OWSBackup.h"
#import "OWSNavigationController.h"
2017-03-23 14:55:39 +01:00
#import "Pastelog.h"
#import "PushManager.h"
#import "RegistrationViewController.h"
#import "Signal-Swift.h"
#import "SignalApp.h"
#import "SignalsNavigationController.h"
#import "ViewControllerUtils.h"
#import <AxolotlKit/SessionCipher.h>
#import <PromiseKit/AnyPromise.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/OWSContactsSyncing.h>
#import <SignalMessaging/OWSMath.h>
2017-12-19 03:50:51 +01:00
#import <SignalMessaging/OWSPreferences.h>
#import <SignalMessaging/OWSProfileManager.h>
2017-12-19 03:50:51 +01:00
#import <SignalMessaging/Release.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>
#import <SignalServiceKit/AppReadiness.h>
2017-11-28 19:46:26 +01:00
#import <SignalServiceKit/NSUserDefaults+OWS.h>
#import <SignalServiceKit/OWS2FAManager.h>
#import <SignalServiceKit/OWSBatchMessageProcessor.h>
Disappearing Messages * Per thread settings menu accessed by tapping on thread title This removed the toggle-phone behavior. You'll be able to see the phone number in the settings table view. This removed the "add contact" functionality, although it was already broken for ios>=9 (which is basically everybody). The group actions menu was absorbed into this screen * Added a confirm alert to leave group (fixes #938) * New Translation Strings * Extend "Add People" label to fit translations. * resolved issues with translations not fitting in group menu * Fix the long standing type warning where TSCalls were assigned to a TSMessageAdapter. * Can delete info messages Follow the JSQMVC pattern and put UIResponder-able content in the messageBubbleContainer. This gives us more functionality *and* allows us to delete some code. yay! It's still not yet possible to delete phone messages. =( * Fixed some compiler warnings. * xcode8 touching storyboard. So long xcode7! * Fixup multiline info messages. We were seeing info messages like "You set disappearing message timer to 10" instead of "You set disappearing message timer to 10 seconds." Admittedly this isn't a very good fix, as now one liners feel like they have too much padding. If the message is well over one line, we were wrapping properly, but there's a problem when the message is *just barely* two lines, the cell height grows, but the label still thinks it's just one line (as evinced by the one line appearing in the center of the label frame. The result being that the last word of the label is cropped. * Disable group actions after leaving group. // FREEBIE
2016-09-21 14:37:51 +02:00
#import <SignalServiceKit/OWSDisappearingMessagesJob.h>
#import <SignalServiceKit/OWSFailedAttachmentDownloadsJob.h>
#import <SignalServiceKit/OWSFailedMessagesJob.h>
#import <SignalServiceKit/OWSMessageManager.h>
#import <SignalServiceKit/OWSMessageSender.h>
#import <SignalServiceKit/OWSOrphanedDataCleaner.h>
#import <SignalServiceKit/OWSPrimaryStorage+Calling.h>
2017-09-21 22:58:07 +02:00
#import <SignalServiceKit/OWSReadReceiptManager.h>
#import <SignalServiceKit/TSAccountManager.h>
#import <SignalServiceKit/TSDatabaseView.h>
2017-02-10 22:24:48 +01:00
#import <SignalServiceKit/TSPreKeyManager.h>
#import <SignalServiceKit/TSSocketManager.h>
#import <SignalServiceKit/TextSecureKitEnv.h>
2018-01-24 23:11:18 +01:00
#import <YapDatabase/YapDatabaseCryptoUtils.h>
#import <sys/sysctl.h>
@import WebRTC;
@import Intents;
NSString *const AppDelegateStoryboardMain = @"Main";
static NSString *const kInitialViewControllerIdentifier = @"UserInitialViewController";
static NSString *const kURLSchemeSGNLKey = @"sgnl";
static NSString *const kURLHostVerifyPrefix = @"verify";
2017-11-29 20:51:39 +01:00
@interface AppDelegate ()
2014-05-06 19:41:08 +02:00
@property (nonatomic) BOOL hasInitialRootViewController;
@property (nonatomic) BOOL areVersionMigrationsComplete;
@property (nonatomic) BOOL didAppLaunchFail;
2018-03-21 20:12:00 +01:00
// Unlike UIApplication.applicationState, this state is
// updated conservatively, e.g. the flag is cleared during
// "will enter background."
@property (nonatomic) BOOL appIsInactive;
@property (nonatomic, nullable) NSDate *appBecameInactiveDate;
@property (nonatomic) UIWindow *screenBlockingWindow;
@property (nonatomic) BOOL hasUnlockedScreenLock;
@property (nonatomic) BOOL isShowingScreenLockUI;
2014-05-06 19:41:08 +02:00
@end
#pragma mark -
@implementation AppDelegate
2014-05-06 19:41:08 +02:00
@synthesize window = _window;
2017-01-17 23:10:57 +01:00
- (void)applicationDidEnterBackground:(UIApplication *)application {
2017-11-08 19:03:51 +01:00
DDLogWarn(@"%@ applicationDidEnterBackground.", self.logTag);
[DDLog flushLog];
2018-03-21 20:12:00 +01:00
self.appIsInactive = YES;
2017-01-17 23:10:57 +01:00
}
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)applicationWillEnterForeground:(UIApplication *)application {
2017-11-08 19:03:51 +01:00
DDLogWarn(@"%@ applicationWillEnterForeground.", self.logTag);
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
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
}
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
{
2017-11-08 19:03:51 +01:00
DDLogWarn(@"%@ applicationDidReceiveMemoryWarning.", self.logTag);
}
- (void)applicationWillTerminate:(UIApplication *)application
{
2017-11-08 19:03:51 +01:00
DDLogWarn(@"%@ applicationWillTerminate.", self.logTag);
[DDLog flushLog];
}
2014-05-06 19:41:08 +02:00
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
2017-11-29 17:01:30 +01: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
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];
}
2017-11-08 19:03:51 +01:00
DDLogWarn(@"%@ application: didFinishLaunchingWithOptions.", self.logTag);
2017-01-24 20:40:44 +01:00
SetRandFunctionSeed();
// 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
// We need to do this _after_ we set up logging, when the keychain is unlocked,
// but before we access YapDatabase, files on disk, or NSUserDefaults
if (![self ensureIsReadyForAppExtensions]) {
// If this method has failed; do nothing.
//
// ensureIsReadyForAppExtensions will show a failure mode UI that
// lets users report this error.
DDLogInfo(@"%@ application: didFinishLaunchingWithOptions failed.", self.logTag);
return YES;
}
[AppVersion instance];
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];
2017-12-07 19:53:13 +01:00
[AppSetup setupEnvironment:^{
return SignalApp.sharedApp.callMessageHandler;
}
notificationsProtocolBlock:^{
return SignalApp.sharedApp.notificationsManager;
}];
[UIUtil applySignalAppearence];
2017-12-04 18:38:44 +01:00
if (CurrentAppContext().isRunningTests) {
return YES;
}
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Show the launch screen until the async database view registrations are complete.
self.window.rootViewController = [self loadingRootViewController];
[self.window makeKeyAndVisible];
// performUpdateCheck must be invoked after Environment has been initialized because
// upgrade process may depend on Environment.
[VersionMigrations performUpdateCheckWithCompletion:^{
OWSAssertIsOnMainThread();
[self versionMigrationsDidComplete];
}];
// Accept push notification when app is not open
NSDictionary *remoteNotif = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
2014-05-06 19:41:08 +02:00
if (remoteNotif) {
2014-07-20 00:25:22 +02:00
DDLogInfo(@"Application was launched by tapping a push notification.");
[self application:application didReceiveRemoteNotification:remoteNotif];
2014-05-06 19:41:08 +02:00
}
[self prepareScreenProtection];
2017-12-07 19:53:13 +01:00
// Ensure OWSContactsSyncing is instantiated.
2017-12-06 21:53:19 +01:00
[OWSContactsSyncing sharedManager];
[[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];
2018-03-21 20:12:00 +01:00
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(screenLockDidChange:)
name:OWSScreenLock.ScreenLockDidChange
object:nil];
2017-11-08 19:03:51 +01:00
DDLogInfo(@"%@ application: didFinishLaunchingWithOptions completed.", self.logTag);
2017-07-21 17:49:38 +02:00
[OWSAnalytics appLaunchDidBegin];
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
{
2017-12-05 19:44:25 +01:00
if ([UIApplication sharedApplication].applicationState != UIApplicationStateBackground) {
2017-12-05 17:35:43 +01:00
return;
}
if (![OWSPrimaryStorage isDatabasePasswordAccessible]) {
2017-12-05 17:35:43 +01:00
DDLogInfo(
@"%@ exiting because we are in the background and the database password is not accessible.", self.logTag);
[DDLog flushLog];
exit(0);
}
}
- (BOOL)ensureIsReadyForAppExtensions
2017-11-28 19:46:26 +01:00
{
// Given how sensitive this migration is, we verbosely
// log the contents of all involved paths before and after.
//
// TODO: Remove this logging once we have high confidence
// in our migration logic.
NSArray<NSString *> *paths = @[
OWSPrimaryStorage.legacyDatabaseFilePath,
OWSPrimaryStorage.legacyDatabaseFilePath_SHM,
OWSPrimaryStorage.legacyDatabaseFilePath_WAL,
OWSPrimaryStorage.sharedDataDatabaseFilePath,
OWSPrimaryStorage.sharedDataDatabaseFilePath_SHM,
OWSPrimaryStorage.sharedDataDatabaseFilePath_WAL,
];
NSFileManager *fileManager = [NSFileManager defaultManager];
for (NSString *path in paths) {
if ([fileManager fileExistsAtPath:path]) {
DDLogInfo(@"%@ storage file: %@, %@", self.logTag, path, [OWSFileSystem fileSizeOfPath:path]);
}
}
2017-11-28 22:30:08 +01:00
if ([OWSPreferences isReadyForAppExtensions]) {
return YES;
2017-11-28 19:46:26 +01:00
}
2018-02-22 19:45:19 +01:00
OWSBackgroundTask *_Nullable backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__];
if ([NSFileManager.defaultManager fileExistsAtPath:OWSPrimaryStorage.legacyDatabaseFilePath]) {
DDLogInfo(@"%@ Legacy Database file size: %@",
2018-01-30 18:39:27 +01:00
self.logTag,
[OWSFileSystem fileSizeOfPath:OWSPrimaryStorage.legacyDatabaseFilePath]);
DDLogInfo(@"%@ \t Legacy SHM file size: %@",
2018-01-30 18:39:27 +01:00
self.logTag,
[OWSFileSystem fileSizeOfPath:OWSPrimaryStorage.legacyDatabaseFilePath_SHM]);
DDLogInfo(@"%@ \t Legacy WAL file size: %@",
2018-01-30 18:39:27 +01:00
self.logTag,
[OWSFileSystem fileSizeOfPath:OWSPrimaryStorage.legacyDatabaseFilePath_WAL]);
2018-01-30 18:39:27 +01:00
}
2018-01-29 16:58:38 +01:00
2018-01-24 23:11:18 +01:00
NSError *_Nullable error = [self convertDatabaseIfNecessary];
if (!error) {
[NSUserDefaults migrateToSharedUserDefaults];
}
if (!error) {
error = [OWSPrimaryStorage migrateToSharedData];
}
if (!error) {
error = [OWSProfileManager migrateToSharedData];
}
if (!error) {
error = [TSAttachmentStream migrateToSharedData];
}
if (error) {
OWSFail(@"%@ database conversion failed: %@", self.logTag, error);
[self showLaunchFailureUI:error];
return NO;
}
2018-01-18 22:32:15 +01:00
2018-02-22 19:45:19 +01:00
backgroundTask = nil;
return YES;
}
- (void)showLaunchFailureUI:(NSError *)error
{
// Disable normal functioning of app.
self.didAppLaunchFail = YES;
// We perform a subset of the [application:didFinishLaunchingWithOptions:].
[AppVersion instance];
[self startupLogging];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Show the launch screen until the async database view registrations are complete.
2018-02-20 19:46:21 +01:00
//
// Note: we void using loadingRootViewController, since it will indirectly try to
// instantiate primary storage, which will fail.
self.window.rootViewController = [self loadingRootViewControllerWithShowUpgradeWarning:NO];
[self.window makeKeyAndVisible];
UIAlertController *controller =
[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];
[controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"SETTINGS_ADVANCED_SUBMIT_DEBUGLOG", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
[Pastelog submitLogsWithCompletion:^{
DDLogInfo(
@"%@ exiting after sharing debug logs.", self.logTag);
[DDLog flushLog];
exit(0);
}];
}]];
UIViewController *fromViewController = [[UIApplication sharedApplication] frontmostViewController];
[fromViewController presentViewController:controller animated:YES completion:nil];
2017-11-28 19:46:26 +01:00
}
2018-01-24 23:11:18 +01:00
- (nullable NSError *)convertDatabaseIfNecessary
{
DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__);
NSString *databaseFilePath = [OWSPrimaryStorage legacyDatabaseFilePath];
if (![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]) {
DDLogVerbose(@"%@ no legacy database file found", self.logTag);
return nil;
}
2018-01-24 23:11:18 +01:00
NSError *_Nullable error;
NSData *_Nullable databasePassword = [OWSStorage tryToLoadDatabaseLegacyPassphrase:&error];
2018-01-24 23:11:18 +01:00
if (!databasePassword || error) {
return (error
?: OWSErrorWithCodeDescription(
OWSErrorCodeDatabaseConversionFatalError, @"Failed to load database password"));
}
YapRecordDatabaseSaltBlock recordSaltBlock = ^(NSData *saltData) {
2018-01-24 23:11:18 +01:00
DDLogVerbose(@"%@ saltData: %@", self.logTag, saltData.hexadecimalString);
// Derive and store the raw cipher key spec, to avoid the ongoing tax of future KDF
NSData *_Nullable keySpecData =
[YapDatabaseCryptoUtils deriveDatabaseKeySpecForPassword:databasePassword saltData:saltData];
if (!keySpecData) {
DDLogError(@"%@ Failed to derive key spec.", self.logTag);
return NO;
}
[OWSStorage storeDatabaseCipherKeySpec:keySpecData];
return YES;
2018-01-24 23:11:18 +01:00
};
error = [YapDatabaseCryptoUtils convertDatabaseIfNecessary:databaseFilePath
databasePassword:databasePassword
recordSaltBlock:recordSaltBlock];
if (!error) {
[OWSStorage removeLegacyPassphrase];
}
return error;
2018-01-24 23:11:18 +01:00
}
2017-09-14 21:24:31 +02:00
- (void)startupLogging
{
DDLogInfo(@"iOS Version: %@", [UIDevice currentDevice].systemVersion);
NSString *localeIdentifier = [NSLocale.currentLocale objectForKey:NSLocaleIdentifier];
if (localeIdentifier.length > 0) {
DDLogInfo(@"Locale Identifier: %@", localeIdentifier);
}
NSString *countryCode = [NSLocale.currentLocale objectForKey:NSLocaleCountryCode];
if (countryCode.length > 0) {
DDLogInfo(@"Country Code: %@", countryCode);
}
NSString *languageCode = [NSLocale.currentLocale objectForKey:NSLocaleLanguageCode];
if (languageCode.length > 0) {
DDLogInfo(@"Language Code: %@", languageCode);
}
size_t size;
sysctlbyname("hw.machine", NULL, &size, NULL, 0);
char *machine = malloc(size);
sysctlbyname("hw.machine", machine, &size, NULL, 0);
NSString *platform = [NSString stringWithUTF8String:machine];
free(machine);
DDLogInfo(@"iPhone Version: %@", platform);
2017-09-14 21:24:31 +02:00
}
- (UIViewController *)loadingRootViewController
{
NSString *lastLaunchedAppVersion = AppVersion.instance.lastAppVersion;
2017-06-20 22:53:02 +02:00
NSString *lastCompletedLaunchAppVersion = AppVersion.instance.lastCompletedLaunchAppVersion;
// Every time we change or add a database view in such a way that
// might cause a delay on launch, we need to bump this constant.
//
2017-12-11 22:34:37 +01:00
// We upgraded YapDatabase in v2.20.0 and need to regenerate all database views.
NSString *kLastVersionWithDatabaseViewChange = @"2.20.0";
BOOL mayNeedUpgrade = ([TSAccountManager isRegistered] && lastLaunchedAppVersion
&& (!lastCompletedLaunchAppVersion ||
[VersionMigrations isVersion:lastCompletedLaunchAppVersion
lessThan:kLastVersionWithDatabaseViewChange]));
2017-11-08 19:03:51 +01:00
DDLogInfo(@"%@ mayNeedUpgrade: %d", self.logTag, mayNeedUpgrade);
2018-02-20 19:46:21 +01:00
return [self loadingRootViewControllerWithShowUpgradeWarning:mayNeedUpgrade];
}
- (UIViewController *)loadingRootViewControllerWithShowUpgradeWarning:(BOOL)showUpgradeWarning
{
UIViewController *viewController =
[[UIStoryboard storyboardWithName:@"Launch Screen" bundle:nil] instantiateInitialViewController];
if (!showUpgradeWarning) {
return viewController;
}
UIView *rootView = viewController.view;
UIImageView *iconView = nil;
for (UIView *subview in viewController.view.subviews) {
if ([subview isKindOfClass:[UIImageView class]]) {
iconView = (UIImageView *)subview;
break;
}
}
2018-02-20 19:46:21 +01:00
if (!iconView) {
OWSFail(@"Database view registration overlay has unexpected contents.");
return viewController;
}
UILabel *bottomLabel = [UILabel new];
bottomLabel.text = NSLocalizedString(
@"DATABASE_VIEW_OVERLAY_SUBTITLE", @"Subtitle shown while the app is updating its database.");
bottomLabel.font = [UIFont ows_mediumFontWithSize:16.f];
bottomLabel.textColor = [UIColor whiteColor];
bottomLabel.numberOfLines = 0;
bottomLabel.lineBreakMode = NSLineBreakByWordWrapping;
bottomLabel.textAlignment = NSTextAlignmentCenter;
[rootView addSubview:bottomLabel];
UILabel *topLabel = [UILabel new];
topLabel.text
= NSLocalizedString(@"DATABASE_VIEW_OVERLAY_TITLE", @"Title shown while the app is updating its database.");
topLabel.font = [UIFont ows_mediumFontWithSize:20.f];
topLabel.textColor = [UIColor whiteColor];
topLabel.numberOfLines = 0;
topLabel.lineBreakMode = NSLineBreakByWordWrapping;
topLabel.textAlignment = NSTextAlignmentCenter;
[rootView addSubview:topLabel];
[bottomLabel autoPinWidthToSuperviewWithMargin:20.f];
[topLabel autoPinWidthToSuperviewWithMargin:20.f];
[bottomLabel autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:topLabel withOffset:10.f];
[iconView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:bottomLabel withOffset:40.f];
return viewController;
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
2018-02-13 04:26:35 +01:00
OWSFail(@"%@ %s app launch failed", self.logTag, __PRETTY_FUNCTION__);
return;
}
2017-11-08 19:03:51 +01:00
DDLogInfo(@"%@ registered vanilla push token: %@", self.logTag, deviceToken);
[PushRegistrationManager.sharedManager didReceiveVanillaPushToken:deviceToken];
2014-05-06 19:41:08 +02:00
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
2018-02-13 04:26:35 +01:00
OWSFail(@"%@ %s app launch failed", self.logTag, __PRETTY_FUNCTION__);
return;
}
2017-11-08 19:03:51 +01:00
DDLogError(@"%@ failed to register vanilla push token with error: %@", self.logTag, error);
#ifdef DEBUG
2017-11-08 19:03:51 +01:00
DDLogWarn(
@"%@ We're in debug mode. Faking success for remote registration with a fake push identifier", self.logTag);
[PushRegistrationManager.sharedManager didReceiveVanillaPushToken:[[NSMutableData dataWithLength:32] copy]];
#else
OWSProdError([OWSAnalyticsEvents appDelegateErrorFailedToRegisterForRemoteNotifications]);
[PushRegistrationManager.sharedManager didFailToReceiveVanillaPushTokenWithError:error];
#endif
}
- (void)application:(UIApplication *)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
2018-02-13 04:26:35 +01:00
OWSFail(@"%@ %s app launch failed", self.logTag, __PRETTY_FUNCTION__);
return;
}
2017-11-08 19:03:51 +01:00
DDLogInfo(@"%@ registered user notification settings", self.logTag);
[PushRegistrationManager.sharedManager didRegisterUserNotificationSettings];
2014-05-06 19:41:08 +02:00
}
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
2018-02-13 04:26:35 +01:00
OWSFail(@"%@ %s app launch failed", self.logTag, __PRETTY_FUNCTION__);
return NO;
}
if (!AppReadiness.isAppReady) {
DDLogWarn(@"%@ Ignoring openURL: app not ready.", self.logTag);
// We don't need to use [AppReadiness runNowOrWhenAppIsReady:];
// the only URLs we handle in Signal iOS at the moment are used
// for resuming the verification step of the registration flow.
return NO;
}
if ([url.scheme isEqualToString:kURLSchemeSGNLKey]) {
if ([url.host hasPrefix:kURLHostVerifyPrefix] && ![TSAccountManager isRegistered]) {
2017-12-04 16:56:59 +01:00
id signupController = SignalApp.sharedApp.signUpFlowNavigationController;
if ([signupController isKindOfClass:[UINavigationController class]]) {
UINavigationController *navController = (UINavigationController *)signupController;
UIViewController *controller = [navController.childViewControllers lastObject];
if ([controller isKindOfClass:[CodeVerificationViewController class]]) {
CodeVerificationViewController *cvvc = (CodeVerificationViewController *)controller;
NSString *verificationCode = [url.path substringFromIndex:1];
[cvvc setVerificationCodeAndTryToVerify:verificationCode];
return YES;
} else {
DDLogWarn(@"Not the verification view controller we expected. Got %@ instead",
NSStringFromClass(controller.class));
}
2014-11-25 23:52:53 +01:00
}
} else {
OWSFail(@"Application opened with an unknown URL action: %@", url.host);
}
2014-11-25 23:52:53 +01:00
} else {
OWSFail(@"Application opened with an unknown URL scheme: %@", url.scheme);
2014-11-25 23:52:53 +01:00
}
return NO;
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
2018-02-13 04:26:35 +01:00
OWSFail(@"%@ %s app launch failed", self.logTag, __PRETTY_FUNCTION__);
return;
}
2017-11-08 19:03:51 +01:00
DDLogWarn(@"%@ applicationDidBecomeActive.", self.logTag);
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 runNowOrWhenAppIsReady:^{
[self handleActivation];
}];
2018-03-21 20:12:00 +01:00
self.appIsInactive = NO;
DDLogInfo(@"%@ applicationDidBecomeActive completed.", self.logTag);
}
- (void)enableBackgroundRefreshIfNecessary
{
[AppReadiness runNowOrWhenAppIsReady:^{
if (OWS2FAManager.sharedManager.is2FAEnabled && [TSAccountManager isRegistered]) {
// Ping server once a day to keep-alive 2FA clients.
const NSTimeInterval kBackgroundRefreshInterval = 24 * 60 * 60;
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:kBackgroundRefreshInterval];
} else {
[[UIApplication sharedApplication]
setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalNever];
}
}];
}
- (void)handleActivation
{
OWSAssertIsOnMainThread();
DDLogWarn(@"%@ handleActivation.", self.logTag);
// Always check prekeys after app launches, and sometimes check on app activation.
[TSPreKeyManager checkPreKeysIfNecessary];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RTCInitializeSSL();
if ([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), ^{
2017-11-08 19:03:51 +01:00
DDLogInfo(@"%@ running post launch block for registered user: %@",
self.logTag,
[TSAccountManager localNumber]);
// Clean up any messages that expired since last launch immediately
// and continue cleaning in the background.
[[OWSDisappearingMessagesJob sharedJob] 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:[OWSPrimaryStorage sharedManager]] run];
[[[OWSFailedAttachmentDownloadsJob alloc] initWithPrimaryStorage:[OWSPrimaryStorage sharedManager]]
run];
[AppStoreRating setupRatingLibrary];
});
} else {
2017-11-08 19:03:51 +01:00
DDLogInfo(@"%@ running post launch block for unregistered user.", self.logTag);
// Unregistered user should have no unread messages. e.g. if you delete your account.
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
[TSSocketManager 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 ([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(), ^{
[TSSocketManager requestSocketOpen];
[[Environment current].contactsManager fetchSystemContactsOnceIfAlreadyAuthorized];
// This will fetch new messages, if we're using domain fronting.
[[PushManager sharedManager] applicationDidBecomeActive];
if (![UIApplication sharedApplication].isRegisteredForRemoteNotifications) {
DDLogInfo(
2017-11-08 19:03:51 +01:00
@"%@ Retrying to register for remote notifications since user hasn't registered yet.", self.logTag);
// 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:SignalApp.sharedApp.accountManager
preferences:[Environment preferences]];
}
if ([OWS2FAManager sharedManager].isDueForReminder) {
if (!self.hasInitialRootViewController || self.window.rootViewController == nil) {
DDLogDebug(
@"%@ Skipping 2FA reminder since there isn't yet an initial view controller", self.logTag);
} else {
UIViewController *rootViewController = self.window.rootViewController;
UINavigationController *reminderNavController =
[OWS2FAReminderViewController wrappedInNavController];
[rootViewController presentViewController:reminderNavController animated:YES completion:nil];
}
}
});
}
2017-07-06 00:12:06 +02:00
DDLogInfo(@"%@ handleActivation completed.", self.logTag);
}
- (void)applicationWillResignActive:(UIApplication *)application {
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
2018-02-13 04:26:35 +01:00
OWSFail(@"%@ %s app launch failed", self.logTag, __PRETTY_FUNCTION__);
return;
}
2017-11-08 19:03:51 +01:00
DDLogWarn(@"%@ applicationWillResignActive.", self.logTag);
2018-03-21 20:12:00 +01:00
self.appIsInactive = YES;
2017-12-15 19:03:03 +01:00
2018-03-21 20:12:00 +01:00
__block OWSBackgroundTask *backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__];
[AppReadiness runNowOrWhenAppIsReady:^{
if ([TSAccountManager isRegistered]) {
2018-03-21 20:12:00 +01:00
[SignalApp.sharedApp.homeViewController updateInboxCountLabel];
}
2018-03-21 20:12:00 +01:00
backgroundTask = nil;
}];
[DDLog flushLog];
}
2015-10-31 23:13:28 +01:00
- (void)application:(UIApplication *)application
performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem
completionHandler:(void (^)(BOOL succeeded))completionHandler {
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
2018-02-13 04:26:35 +01:00
OWSFail(@"%@ %s app launch failed", self.logTag, __PRETTY_FUNCTION__);
return;
}
[AppReadiness runNowOrWhenAppIsReady:^{
if (![TSAccountManager isRegistered]) {
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 showNewConversationView];
2018-02-13 04:41:52 +01:00
completionHandler(YES);
}];
2015-10-31 23:13:28 +01:00
}
/**
* Among other things, this is used by "call back" callkit dialog and calling from native contacts app.
2018-02-13 04:41:52 +01:00
*
* We always return YES if we are going to try to handle the user activity since
* we never want iOS to contact us again using a URL.
*
* From https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623072-application?language=objc:
*
* If you do not implement this method or if your implementation returns NO, iOS tries to
* create a document for your app to open using a URL.
*/
- (BOOL)application:(UIApplication *)application
continueUserActivity:(nonnull NSUserActivity *)userActivity
restorationHandler:(nonnull void (^)(NSArray *_Nullable))restorationHandler
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
2018-02-13 04:26:35 +01:00
OWSFail(@"%@ %s app launch failed", self.logTag, __PRETTY_FUNCTION__);
return NO;
}
2017-01-18 23:29:47 +01:00
if ([userActivity.activityType isEqualToString:@"INStartVideoCallIntent"]) {
if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(10, 0)) {
2017-11-08 19:03:51 +01:00
DDLogError(@"%@ unexpectedly received INStartVideoCallIntent pre iOS10", self.logTag);
return NO;
}
2017-11-08 19:03:51 +01:00
DDLogInfo(@"%@ got start video call intent", self.logTag);
INInteraction *interaction = [userActivity interaction];
INIntent *intent = interaction.intent;
if (![intent isKindOfClass:[INStartVideoCallIntent class]]) {
2017-11-08 19:03:51 +01:00
DDLogError(@"%@ unexpected class for start call video: %@", self.logTag, intent);
return NO;
}
INStartVideoCallIntent *startCallIntent = (INStartVideoCallIntent *)intent;
NSString *_Nullable handle = startCallIntent.contacts.firstObject.personHandle.value;
if (!handle) {
2017-11-08 19:03:51 +01:00
DDLogWarn(@"%@ unable to find handle in startCallIntent: %@", self.logTag, startCallIntent);
return NO;
}
[AppReadiness runNowOrWhenAppIsReady:^{
NSString *_Nullable phoneNumber = handle;
if ([handle hasPrefix:CallKitCallManager.kAnonymousCallHandlePrefix]) {
phoneNumber = [[OWSPrimaryStorage sharedManager] phoneNumberForCallKitId:handle];
if (phoneNumber.length < 1) {
DDLogWarn(
@"%@ ignoring attempt to initiate video call to unknown anonymous signal user.", self.logTag);
return;
}
}
// This intent can be received from more than one user interaction.
//
// * It can be received if the user taps the "video" button in the CallKit UI for an
// an ongoing call. If so, the correct response is to try to activate the local
// video for that call.
// * It can be received if the user taps the "video" button for a contact in the
// contacts app. If so, the correct response is to try to initiate a new call
// to that user - unless there already is another call in progress.
if (SignalApp.sharedApp.callService.call != nil) {
if ([phoneNumber isEqualToString:SignalApp.sharedApp.callService.call.remotePhoneNumber]) {
DDLogWarn(@"%@ trying to upgrade ongoing call to video.", self.logTag);
[SignalApp.sharedApp.callService handleCallKitStartVideo];
return;
} else {
DDLogWarn(@"%@ ignoring INStartVideoCallIntent due to ongoing WebRTC call with another party.",
self.logTag);
return;
}
}
OutboundCallInitiator *outboundCallInitiator = SignalApp.sharedApp.outboundCallInitiator;
OWSAssert(outboundCallInitiator);
[outboundCallInitiator initiateCallWithHandle:phoneNumber];
}];
return YES;
} else if ([userActivity.activityType isEqualToString:@"INStartAudioCallIntent"]) {
if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(10, 0)) {
2017-11-08 19:03:51 +01:00
DDLogError(@"%@ unexpectedly received INStartAudioCallIntent pre iOS10", self.logTag);
return NO;
}
2017-11-08 19:03:51 +01:00
DDLogInfo(@"%@ got start audio call intent", self.logTag);
INInteraction *interaction = [userActivity interaction];
INIntent *intent = interaction.intent;
if (![intent isKindOfClass:[INStartAudioCallIntent class]]) {
2017-11-08 19:03:51 +01:00
DDLogError(@"%@ unexpected class for start call audio: %@", self.logTag, intent);
return NO;
}
INStartAudioCallIntent *startCallIntent = (INStartAudioCallIntent *)intent;
NSString *_Nullable handle = startCallIntent.contacts.firstObject.personHandle.value;
if (!handle) {
2017-11-08 19:03:51 +01:00
DDLogWarn(@"%@ unable to find handle in startCallIntent: %@", self.logTag, startCallIntent);
return NO;
}
[AppReadiness runNowOrWhenAppIsReady:^{
NSString *_Nullable phoneNumber = handle;
if ([handle hasPrefix:CallKitCallManager.kAnonymousCallHandlePrefix]) {
phoneNumber = [[OWSPrimaryStorage sharedManager] phoneNumberForCallKitId:handle];
if (phoneNumber.length < 1) {
DDLogWarn(
@"%@ ignoring attempt to initiate audio call to unknown anonymous signal user.", self.logTag);
return;
}
}
if (SignalApp.sharedApp.callService.call != nil) {
DDLogWarn(@"%@ ignoring INStartAudioCallIntent due to ongoing WebRTC call.", self.logTag);
return;
}
OutboundCallInitiator *outboundCallInitiator = SignalApp.sharedApp.outboundCallInitiator;
OWSAssert(outboundCallInitiator);
[outboundCallInitiator initiateCallWithHandle:phoneNumber];
}];
return YES;
2017-01-18 23:29:47 +01:00
} else {
DDLogWarn(@"%@ called %s with userActivity: %@, but not yet supported.",
2017-11-08 19:03:51 +01:00
self.logTag,
__PRETTY_FUNCTION__,
userActivity.activityType);
2017-01-18 23:29:47 +01:00
}
// TODO Something like...
// *phoneNumber = [[[[[[userActivity interaction] intent] contacts] firstObject] personHandle] value]
// thread = blah
// [callUIAdapter startCall:thread]
//
// Here's the Speakerbox Example for intent / NSUserActivity handling:
//
// func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
// guard let handle = userActivity.startCallHandle else {
// print("Could not determine start call handle from user activity: \(userActivity)")
// return false
// }
//
// guard let video = userActivity.video else {
// print("Could not determine video from user activity: \(userActivity)")
// return false
// }
//
// callManager.startCall(handle: handle, video: video)
// return true
// }
2018-02-13 04:41:52 +01:00
return NO;
}
#pragma mark Push Notifications Delegate Methods
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
2018-02-13 04:26:35 +01:00
OWSFail(@"%@ %s app launch failed", self.logTag, __PRETTY_FUNCTION__);
return;
}
// It is safe to continue even if the app isn't ready.
[[PushManager sharedManager] application:application didReceiveRemoteNotification:userInfo];
}
2017-01-17 23:10:57 +01:00
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
2018-02-13 04:26:35 +01:00
OWSFail(@"%@ %s app launch failed", self.logTag, __PRETTY_FUNCTION__);
return;
}
// It is safe to continue even if the app isn't ready.
[[PushManager sharedManager] application:application
didReceiveRemoteNotification:userInfo
fetchCompletionHandler:completionHandler];
}
- (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) {
2018-02-13 04:26:35 +01:00
OWSFail(@"%@ %s app launch failed", self.logTag, __PRETTY_FUNCTION__);
return;
}
2017-11-08 19:03:51 +01:00
DDLogInfo(@"%@ %s %@", self.logTag, __PRETTY_FUNCTION__, notification);
[AppStoreRating preventPromptAtNextTest];
[AppReadiness runNowOrWhenAppIsReady:^{
[[PushManager sharedManager] application:application didReceiveLocalNotification:notification];
}];
}
- (void)application:(UIApplication *)application
handleActionWithIdentifier:(NSString *)identifier
forLocalNotification:(UILocalNotification *)notification
completionHandler:(void (^)(void))completionHandler
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
2018-02-13 04:26:35 +01:00
OWSFail(@"%@ %s app launch failed", self.logTag, __PRETTY_FUNCTION__);
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 runNowOrWhenAppIsReady:^{
[[PushManager sharedManager] application:application
handleActionWithIdentifier:identifier
forLocalNotification:notification
completionHandler:completionHandler];
}];
}
- (void)application:(UIApplication *)application
handleActionWithIdentifier:(NSString *)identifier
forLocalNotification:(UILocalNotification *)notification
withResponseInfo:(NSDictionary *)responseInfo
completionHandler:(void (^)(void))completionHandler
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
2018-02-13 04:26:35 +01:00
OWSFail(@"%@ %s app launch failed", self.logTag, __PRETTY_FUNCTION__);
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 runNowOrWhenAppIsReady:^{
[[PushManager sharedManager] application:application
handleActionWithIdentifier:identifier
forLocalNotification:notification
withResponseInfo: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
{
DDLogInfo(@"%@ performing background fetch", self.logTag);
[AppReadiness runNowOrWhenAppIsReady:^{
__block AnyPromise *job = [[SignalApp sharedApp].messageFetcherJob run].then(^{
// HACK: Call completion handler after n seconds.
//
// We don't currently have a convenient API to know when message fetching is *done* when
// working with the websocket.
//
// We *could* substantially rewrite the TSSocketManager to take advantage of the `empty` message
// But once our REST endpoint is fixed to properly de-enqueue fallback notifications, we can easily
// use the rest endpoint here rather than the websocket and circumvent making changes to critical code.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
completionHandler(UIBackgroundFetchResultNewData);
job = nil;
});
});
}];
}
- (void)versionMigrationsDidComplete
{
OWSAssertIsOnMainThread();
DDLogInfo(@"%@ versionMigrationsDidComplete", self.logTag);
self.areVersionMigrationsComplete = YES;
[self checkIfAppIsReady];
}
2017-12-19 04:56:02 +01:00
- (void)storageIsReady
{
OWSAssertIsOnMainThread();
2017-12-19 04:56:02 +01:00
DDLogInfo(@"%@ storageIsReady", self.logTag);
[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;
}
DDLogInfo(@"%@ checkIfAppIsReady", self.logTag);
2018-02-12 16:00:20 +01:00
// TODO: Once "app ready" logic is moved into AppSetup, move this line there.
[[OWSProfileManager sharedManager] 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];
2017-09-14 21:30:22 +02:00
if ([TSAccountManager isRegistered]) {
DDLogInfo(@"localNumber: %@", [TSAccountManager localNumber]);
// Fetch messages as soon as possible after launching. In particular, when
// launching from the background, without this, we end up waiting some extra
// seconds before receiving an actionable push notification.
__unused AnyPromise *messagePromise = [SignalApp.sharedApp.messageFetcherJob run];
// This should happen at any launch, background or foreground.
__unused AnyPromise *pushTokenpromise =
[OWSSyncPushTokensJob runWithAccountManager:SignalApp.sharedApp.accountManager
preferences:[Environment preferences]];
2017-09-14 21:30:22 +02:00
}
[DeviceSleepManager.sharedInstance removeBlockWithBlockObject:self];
2018-02-12 16:00:20 +01:00
[AppVersion.instance mainAppLaunchDidComplete];
2017-12-14 17:42:57 +01:00
[Environment.current.contactsManager loadSignalAccountsFromCache];
2017-12-14 17:43:27 +01:00
// If there were any messages in our local queue which we hadn't yet processed.
[[OWSMessageReceiver sharedInstance] handleAnyUnprocessedEnvelopesAsync];
[[OWSBatchMessageProcessor sharedInstance] handleAnyUnprocessedEnvelopesAsync];
if (!Environment.preferences.hasGeneratedThumbnails) {
[OWSPrimaryStorage.sharedManager.newDatabaseConnection
asyncReadWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[TSAttachmentStream enumerateCollectionObjectsUsingBlock:^(id _Nonnull obj, BOOL *_Nonnull stop){
// no-op. It's sufficient to initWithCoder: each object.
}];
}
completionBlock:^{
[Environment.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
// of disk access. We might want to only run it "once per version"
// or something like that in production.
[OWSOrphanedDataCleaner auditAndCleanupAsync:nil];
#endif
2017-08-29 22:31:27 +02:00
[OWSProfileManager.sharedManager fetchLocalUsersProfile];
2017-09-25 20:45:50 +02:00
[[OWSReadReceiptManager sharedManager] 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
[OWSBackup.sharedManager setup];
2018-03-21 20:12:00 +01:00
[self ensureScreenProtection];
}
- (void)registrationStateDidChange
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread();
DDLogInfo(@"registrationStateDidChange");
[self enableBackgroundRefreshIfNecessary];
if ([TSAccountManager isRegistered]) {
DDLogInfo(@"localNumber: %@", [TSAccountManager localNumber]);
[[OWSPrimaryStorage sharedManager].newDatabaseConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
2017-12-07 16:33:27 +01:00
[ExperienceUpgradeFinder.sharedManager markAllAsSeenWithTransaction:transaction];
}];
// Start running the disappearing messages job in case the newly registered user
// enables this feature
[[OWSDisappearingMessagesJob sharedJob] startIfNecessary];
[[OWSProfileManager sharedManager] ensureLocalProfileCached];
// For non-legacy users, read receipts are on by default.
[OWSReadReceiptManager.sharedManager setAreReadReceiptsEnabled:YES];
}
2018-03-21 20:12:00 +01:00
[self ensureScreenProtection];
}
- (void)registrationLockDidChange:(NSNotification *)notification
{
[self enableBackgroundRefreshIfNecessary];
}
- (void)ensureRootViewController
{
OWSAssertIsOnMainThread();
2017-11-08 19:03:51 +01:00
DDLogInfo(@"%@ ensureRootViewController", self.logTag);
if (!AppReadiness.isAppReady || self.hasInitialRootViewController) {
return;
}
self.hasInitialRootViewController = YES;
DDLogInfo(@"%@ Presenting initial root view controller", self.logTag);
if ([TSAccountManager isRegistered]) {
2017-09-06 19:59:39 +02:00
HomeViewController *homeView = [HomeViewController new];
SignalsNavigationController *navigationController =
[[SignalsNavigationController alloc] initWithRootViewController:homeView];
self.window.rootViewController = navigationController;
} else {
RegistrationViewController *viewController = [RegistrationViewController new];
OWSNavigationController *navigationController =
[[OWSNavigationController alloc] initWithRootViewController:viewController];
navigationController.navigationBarHidden = YES;
self.window.rootViewController = navigationController;
}
[AppUpdateNag.sharedInstance showAppUpgradeNagIfNecessary];
2018-03-21 20:12:00 +01:00
[self ensureScreenProtection];
}
#pragma mark - Screen Lock and Protection
- (void)setAppIsInactive:(BOOL)appIsInactive
{
if (appIsInactive) {
if (!_appIsInactive) {
// Whenever app becomes inactive, clear this state.
self.hasUnlockedScreenLock = NO;
// Note the time when app became inactive.
self.appBecameInactiveDate = [NSDate new];
}
}
_appIsInactive = appIsInactive;
[self ensureScreenProtection];
}
- (void)ensureScreenProtection
{
OWSAssertIsOnMainThread();
if (!AppReadiness.isAppReady) {
[AppReadiness runNowOrWhenAppIsReady:^{
[self ensureScreenProtection];
}];
return;
}
// Don't show 'Screen Protection' if:
//
// * App is active or...
// * 'Screen Protection' is not enabled.
BOOL shouldHaveScreenProtection = (self.appIsInactive && Environment.preferences.screenSecurityIsEnabled);
BOOL shouldHaveScreenLock = NO;
if (self.appIsInactive) {
// Don't show 'Screen Lock' if app is inactive.
} else if (![TSAccountManager isRegistered]) {
// Don't show 'Screen Lock' if user is not registered.
} else if (!OWSScreenLock.sharedManager.isScreenLockEnabled) {
// Don't show 'Screen Lock' if 'Screen Lock' isn't enabled.
} else if (self.hasUnlockedScreenLock) {
// Don't show 'Screen Lock' if 'Screen Lock' has been unlocked.
} else if (!self.appBecameInactiveDate) {
// Show 'Screen Lock' if app hasn't become inactive yet (just launched).
shouldHaveScreenLock = YES;
} else {
OWSAssert(self.appBecameInactiveDate);
NSTimeInterval screenLockInterval = fabs([self.appBecameInactiveDate timeIntervalSinceNow]);
NSTimeInterval screenLockTimeout = OWSScreenLock.sharedManager.screenLockTimeout;
OWSAssert(screenLockInterval >= 0);
OWSAssert(screenLockTimeout >= 0);
if (self.appBecameInactiveDate && screenLockInterval < screenLockTimeout) {
// Don't show 'Screen Lock' if 'Screen Lock' timeout hasn't elapsed.
shouldHaveScreenProtection = YES;
// Check again when screen lock timeout should elapse.
NSTimeInterval screenLockRemaining = screenLockTimeout - screenLockInterval + 0.2f;
OWSAssert(screenLockRemaining >= 0);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(screenLockRemaining * NSEC_PER_SEC)),
dispatch_get_main_queue(),
^{
[self ensureScreenProtection];
});
} else {
// Otherwise, show 'Screen Lock'.
shouldHaveScreenLock = YES;
}
}
BOOL shouldShowBlockWindow = shouldHaveScreenProtection || shouldHaveScreenLock;
self.screenBlockingWindow.hidden = !shouldShowBlockWindow;
if (shouldHaveScreenLock) {
if (!self.isShowingScreenLockUI) {
self.isShowingScreenLockUI = YES;
[OWSScreenLock.sharedManager tryToUnlockScreenLockWithSuccess:^{
DDLogInfo(@"%@ unlock screen lock succeeded.", self.logTag);
self.isShowingScreenLockUI = NO;
self.hasUnlockedScreenLock = YES;
[self ensureScreenProtection];
}
failure:^(NSError *error) {
DDLogInfo(@"%@ unlock screen lock failed.", self.logTag);
self.isShowingScreenLockUI = NO;
[self showScreenLockFailureAlertWithMessage:error.localizedDescription];
}
cancel:^{
DDLogInfo(@"%@ unlock screen lock cancelled.", self.logTag);
self.isShowingScreenLockUI = NO;
[self showScreenLockFailureAlertWithMessage:
NSLocalizedString(@"SCREEN_LOCK_UNLOCK_CANCELLED",
@"Message for alert indicating that screen lock unlock was cancelled.")];
}];
}
}
}
- (void)showScreenLockFailureAlertWithMessage:(NSString *)message
{
OWSAssertIsOnMainThread();
[OWSAlerts showAlertWithTitle:NSLocalizedString(@"SCREEN_LOCK_UNLOCK_FAILED",
@"Title for alert indicating that screen lock could not be unlocked.")
message:message
buttonTitle:nil
buttonAction:^(UIAlertAction *action) {
// After the alert, re-show the unlock UI.
[self ensureScreenProtection];
}];
}
- (void)screenLockDidChange:(NSNotification *)notification
{
[self ensureScreenProtection];
}
// 'Screen Blocking' window obscures the app screen:
//
// * In the app switcher.
// * During 'Screen Lock' unlock process.
- (void)prepareScreenProtection
{
OWSAssertIsOnMainThread();
UIWindow *window = [[UIWindow alloc] initWithFrame:self.window.bounds];
window.hidden = YES;
window.opaque = YES;
window.userInteractionEnabled = NO;
window.windowLevel = CGFLOAT_MAX;
window.backgroundColor = UIColor.ows_materialBlueColor;
window.rootViewController =
[[UIStoryboard storyboardWithName:@"Launch Screen" bundle:nil] instantiateInitialViewController];
self.screenBlockingWindow = window;
}
2014-05-06 19:41:08 +02:00
@end