diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 4bbd3ca9f..e5eb7cb42 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -61,6 +61,7 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; @property (nonatomic) UIWindow *screenProtectionWindow; @property (nonatomic) BOOL hasInitialRootViewController; @property (nonatomic) BOOL areVersionMigrationsComplete; +@property (nonatomic) BOOL didAppLaunchFail; @end @@ -130,7 +131,15 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; // 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 - [self ensureIsReadyForAppExtensions]; + 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]; @@ -219,10 +228,10 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; } } -- (void)ensureIsReadyForAppExtensions +- (BOOL)ensureIsReadyForAppExtensions { if ([OWSPreferences isReadyForAppExtensions]) { - return; + return YES; } if ([NSFileManager.defaultManager fileExistsAtPath:TSStorageManager.legacyDatabaseFilePath]) { @@ -238,14 +247,56 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; } NSError *_Nullable error = [self convertDatabaseIfNecessary]; - // TODO: Handle this error. - OWSAssert(!error); + if (error) { + OWSFail(@"%@ database conversion failed: %@", self.logTag, error); + [self showLaunchFailureUI:error]; + return NO; + } [NSUserDefaults migrateToSharedUserDefaults]; [TSStorageManager migrateToSharedData]; [OWSProfileManager migrateToSharedData]; [TSAttachmentStream migrateToSharedData]; + + 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. + self.window.rootViewController = [self loadingRootViewController]; + + [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 submitLogsWithShareCompletion:^{ + DDLogInfo( + @"%@ exiting after sharing debug logs.", self.logTag); + [DDLog flushLog]; + exit(0); + }]; + }]]; + UIViewController *fromViewController = [[UIApplication sharedApplication] frontmostViewController]; + [fromViewController presentViewController:controller animated:YES completion:nil]; } - (nullable NSError *)convertDatabaseIfNecessary @@ -368,6 +419,11 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; { OWSAssertIsOnMainThread(); + if (self.didAppLaunchFail) { + OWSFail(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); + return; + } + DDLogInfo(@"%@ registered vanilla push token: %@", self.logTag, deviceToken); [PushRegistrationManager.sharedManager didReceiveVanillaPushToken:deviceToken]; } @@ -376,6 +432,11 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; { OWSAssertIsOnMainThread(); + if (self.didAppLaunchFail) { + OWSFail(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); + return; + } + DDLogError(@"%@ failed to register vanilla push token with error: %@", self.logTag, error); #ifdef DEBUG DDLogWarn( @@ -392,6 +453,11 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; { OWSAssertIsOnMainThread(); + if (self.didAppLaunchFail) { + OWSFail(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); + return; + } + DDLogInfo(@"%@ registered user notification settings", self.logTag); [PushRegistrationManager.sharedManager didRegisterUserNotificationSettings]; } @@ -403,6 +469,11 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; { OWSAssertIsOnMainThread(); + if (self.didAppLaunchFail) { + DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); + return NO; + } + if (!AppReadiness.isAppReady) { DDLogWarn(@"%@ Ignoring openURL: app not ready.", self.logTag); // TODO: Consider using [AppReadiness runNowOrWhenAppIsReady:]. @@ -437,6 +508,11 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; - (void)applicationDidBecomeActive:(UIApplication *)application { OWSAssertIsOnMainThread(); + if (self.didAppLaunchFail) { + DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); + return; + } + DDLogWarn(@"%@ applicationDidBecomeActive.", self.logTag); if (CurrentAppContext().isRunningTests) { return; @@ -536,6 +612,11 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; - (void)applicationWillResignActive:(UIApplication *)application { OWSAssertIsOnMainThread(); + if (self.didAppLaunchFail) { + DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); + return; + } + DDLogWarn(@"%@ applicationWillResignActive.", self.logTag); __block OWSBackgroundTask *backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__]; @@ -564,6 +645,11 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; completionHandler:(void (^)(BOOL succeeded))completionHandler { OWSAssertIsOnMainThread(); + if (self.didAppLaunchFail) { + DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); + return; + } + if (!AppReadiness.isAppReady) { DDLogWarn(@"%@ Ignoring performActionForShortcutItem: app not ready.", self.logTag); // TODO: Consider using [AppReadiness runNowOrWhenAppIsReady:]. @@ -602,6 +688,11 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; { OWSAssertIsOnMainThread(); + if (self.didAppLaunchFail) { + DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); + return NO; + } + if (!AppReadiness.isAppReady) { DDLogWarn(@"%@ Ignoring continueUserActivity: app not ready.", self.logTag); // TODO: Consider using [AppReadiness runNowOrWhenAppIsReady:]. @@ -772,6 +863,11 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { OWSAssertIsOnMainThread(); + if (self.didAppLaunchFail) { + DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); + return; + } + // It is safe to continue even if the app isn't ready. [[PushManager sharedManager] application:application didReceiveRemoteNotification:userInfo]; } @@ -781,6 +877,11 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { OWSAssertIsOnMainThread(); + if (self.didAppLaunchFail) { + DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); + return; + } + // It is safe to continue even if the app isn't ready. [[PushManager sharedManager] application:application didReceiveRemoteNotification:userInfo @@ -790,6 +891,11 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification { OWSAssertIsOnMainThread(); + if (self.didAppLaunchFail) { + DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); + return; + } + DDLogInfo(@"%@ %s %@", self.logTag, __PRETTY_FUNCTION__, notification); [AppStoreRating preventPromptAtNextTest]; @@ -805,6 +911,11 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; { OWSAssertIsOnMainThread(); + if (self.didAppLaunchFail) { + DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); + return; + } + if (!AppReadiness.isAppReady) { DDLogWarn(@"%@ Ignoring handleActionWithIdentifier: app not ready.", self.logTag); // TODO: Consider using [AppReadiness runNowOrWhenAppIsReady:]. @@ -825,6 +936,11 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; { OWSAssertIsOnMainThread(); + if (self.didAppLaunchFail) { + DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); + return; + } + if (!AppReadiness.isAppReady) { DDLogWarn(@"%@ Ignoring handleActionWithIdentifier: app not ready.", self.logTag); // TODO: Consider using [AppReadiness runNowOrWhenAppIsReady:]. diff --git a/Signal/src/util/Pastelog.h b/Signal/src/util/Pastelog.h index 6fdcbcad3..99356aa0e 100644 --- a/Signal/src/util/Pastelog.h +++ b/Signal/src/util/Pastelog.h @@ -1,13 +1,15 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // @interface Pastelog : NSObject -typedef void (^successBlock)(NSError *error, NSString *urlString); +typedef void (^DebugLogsUploadedBlock)(NSError *error, NSString *urlString); +typedef void (^DebugLogsSharedBlock)(void); +(void)submitLogs; -+(void)submitLogsWithCompletion:(successBlock)block; -+(void)submitLogsWithCompletion:(successBlock)block forFileLogger:(DDFileLogger*)fileLogger; ++ (void)submitLogsWithShareCompletion:(nullable DebugLogsSharedBlock)block; ++ (void)submitLogsWithUploadCompletion:(DebugLogsUploadedBlock)block; ++ (void)submitLogsWithUploadCompletion:(DebugLogsUploadedBlock)block forFileLogger:(DDFileLogger *)fileLogger; @end diff --git a/Signal/src/util/Pastelog.m b/Signal/src/util/Pastelog.m index 37d452344..d6dc5b7cb 100644 --- a/Signal/src/util/Pastelog.m +++ b/Signal/src/util/Pastelog.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "Pastelog.h" @@ -18,7 +18,7 @@ @property (nonatomic) UIAlertController *loadingAlert; @property (nonatomic) NSMutableData *responseData; -@property (nonatomic) successBlock block; +@property (nonatomic) DebugLogsUploadedBlock block; @end @@ -27,7 +27,20 @@ @implementation Pastelog +(void)submitLogs { - [self submitLogsWithCompletion:^(NSError *error, NSString *urlString) { + [self submitLogsWithShareCompletion:nil]; +} + ++ (void)submitLogsWithShareCompletion:(nullable DebugLogsSharedBlock)shareCompletionParam +{ + DebugLogsSharedBlock shareCompletion = ^{ + if (shareCompletionParam) { + // Wait a moment. If PasteLog opens a URL, it needs a moment to complete. + dispatch_after( + dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), shareCompletionParam); + } + }; + + [self submitLogsWithUploadCompletion:^(NSError *error, NSString *urlString) { if (!error) { UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"DEBUG_LOG_ALERT_TITLE", @"Title of the debug log alert.") @@ -41,6 +54,8 @@ style:UIAlertActionStyleDefault handler:^(UIAlertAction *_Nonnull action) { [Pastelog.sharedManager submitEmail:urlString]; + + shareCompletion(); }]]; [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"DEBUG_LOG_ALERT_OPTION_COPY_LINK", @@ -49,6 +64,8 @@ handler:^(UIAlertAction *_Nonnull action) { UIPasteboard *pb = [UIPasteboard generalPasteboard]; [pb setString:urlString]; + + shareCompletion(); }]]; #ifdef DEBUG [alert addAction:[UIAlertAction @@ -73,7 +90,8 @@ @"Label for the 'Open a Bug Report' option of the the debug log alert.") style:UIAlertActionStyleCancel handler:^(UIAlertAction *_Nonnull action) { - [Pastelog.sharedManager prepareRedirection:urlString]; + [Pastelog.sharedManager prepareRedirection:urlString + shareCompletion:shareCompletion]; }]]; UIViewController *presentingViewController = UIApplication.sharedApplication.frontmostViewControllerIgnoringAlerts; @@ -91,11 +109,13 @@ }]; } -+(void)submitLogsWithCompletion:(successBlock)block { - [self submitLogsWithCompletion:(successBlock)block forFileLogger:[[DDFileLogger alloc] init]]; ++ (void)submitLogsWithUploadCompletion:(DebugLogsUploadedBlock)block +{ + [self submitLogsWithUploadCompletion:block forFileLogger:[[DDFileLogger alloc] init]]; } -+(void)submitLogsWithCompletion:(successBlock)block forFileLogger:(DDFileLogger*)fileLogger { ++ (void)submitLogsWithUploadCompletion:(DebugLogsUploadedBlock)block forFileLogger:(DDFileLogger *)fileLogger +{ [self sharedManager].block = block; @@ -231,7 +251,10 @@ [UIApplication.sharedApplication openURL: [NSURL URLWithString: urlString]]; } -- (void)prepareRedirection:(NSString*)url { +- (void)prepareRedirection:(NSString *)url shareCompletion:(DebugLogsSharedBlock)shareCompletion +{ + OWSAssert(shareCompletion); + UIPasteboard *pb = [UIPasteboard generalPasteboard]; [pb setString:url]; @@ -248,6 +271,8 @@ [UIApplication.sharedApplication openURL:[NSURL URLWithString:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"LOGS_URL"]]]; + + shareCompletion(); }]]; UIViewController *presentingViewController = UIApplication.sharedApplication.frontmostViewControllerIgnoringAlerts; [presentingViewController presentViewController:alert animated:NO completion:nil]; diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index c6d182240..c21d184a5 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -52,6 +52,12 @@ /* No comment provided by engineer. */ "APN_MESSAGE_IN_GROUP_DETAILED" = "%@ in group %@: %@"; +/* Message for the 'app launch failed' alert. */ +"APP_LAUNCH_FAILURE_ALERT_MESSAGE" = "Signal can't launch. Please send your debug logs to our team so that we can try to resolve this issue."; + +/* Title for the 'app launch failed' alert. */ +"APP_LAUNCH_FAILURE_ALERT_TITLE" = "Error"; + /* Text prompting user to edit their profile name. */ "APP_SETTINGS_EDIT_PROFILE_NAME_PROMPT" = "Enter your name";