AppDelegate.m 70 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2012-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. #import <CommonCrypto/CommonCrypto.h>
  21. #import <MobileCoreServices/MobileCoreServices.h>
  22. #import "AppDelegate.h"
  23. #import "NaClCrypto.h"
  24. #import "ServerConnector.h"
  25. #import "MyIdentityStore.h"
  26. #import "MessageQueue.h"
  27. #import "ContactStore.h"
  28. #import "Conversation.h"
  29. #import "UIDefines.h"
  30. #import "ServerAPIConnector.h"
  31. #import "UserSettings.h"
  32. #import "TypingIndicatorManager.h"
  33. #import "StatusNavigationBar.h"
  34. #import "MyIdentityStore.h"
  35. #import "PortraitNavigationController.h"
  36. #import "UserReminder.h"
  37. #import "Utils.h"
  38. #import "Contact.h"
  39. #import "ContactsViewController.h"
  40. #import "ProtocolDefines.h"
  41. #import "PhoneNumberNormalizer.h"
  42. #import "AbstractGroupMessage.h"
  43. #import "NSString+Hex.h"
  44. #import "SVProgressHUD.h"
  45. #import "NewMessageToaster.h"
  46. #import "EntityFetcher.h"
  47. #import "SplitViewController.h"
  48. #import "GatewayAvatarMaker.h"
  49. #import "ErrorHandler.h"
  50. #import "DatabaseManager.h"
  51. #import "FeatureMask.h"
  52. #import "TouchIdAuthentication.h"
  53. #import "ErrorNotificationHandler.h"
  54. #import "SplashViewController.h"
  55. #import "MessageProcessor.h"
  56. #import "MessageProcessorProxy.h"
  57. #import "BackgroundTaskManagerProxy.h"
  58. #import "NotificationManager.h"
  59. #import "SDNetworkActivityIndicator.h"
  60. #import "ActivityIndicatorProxy.h"
  61. #import "BundleUtil.h"
  62. #import "AppGroup.h"
  63. #import "LicenseStore.h"
  64. #import "EnterLicenseViewController.h"
  65. #import "WorkDataFetcher.h"
  66. #import "IdentityBackupStore.h"
  67. #import "URLHandler.h"
  68. #import "MDMSetup.h"
  69. #import "NSString+Hex.h"
  70. #import "BaseMessage.h"
  71. #import "BoxBallotCreateMessage.h"
  72. #import "BallotMessageDecoder.h"
  73. #import "BoxImageMessage.h"
  74. #import "ImageMessage.h"
  75. #import "GroupImageMessage.h"
  76. #import "BoxVideoMessage.h"
  77. #import "VideoMessage.h"
  78. #import "GroupVideoMessage.h"
  79. #import "DocumentManager.h"
  80. #import "MessageSender.h"
  81. #import "EntityManager.h"
  82. #import "VoIPHelper.h"
  83. #import "PushPayloadDecryptor.h"
  84. #import "Threema-Swift.h"
  85. #import "ConversationUtils.h"
  86. #import "ThreemaFramework.h"
  87. #import <AVFoundation/AVFoundation.h>
  88. #import <UserNotifications/UserNotifications.h>
  89. #import <PushKit/PushKit.h>
  90. #import <Intents/Intents.h>
  91. #import "ValidationLogger.h"
  92. #ifdef DEBUG
  93. static const DDLogLevel ddLogLevel = DDLogLevelAll;
  94. #else
  95. static const DDLogLevel ddLogLevel = DDLogLevelNotice;
  96. #endif
  97. @interface AppDelegate () <EnterLicenseDelegate, PKPushRegistryDelegate>
  98. @end
  99. @implementation AppDelegate {
  100. NSDictionary *launchOptions;
  101. BOOL migrating;
  102. BOOL databaseImported;
  103. NSURL *pendingUrl;
  104. BOOL protectedDataWillBecomeUnavailable;
  105. NewMessageToaster *toaster;
  106. UIViewController *lastViewController;
  107. UIApplicationShortcutItem *pendingShortCutItem;
  108. BOOL shouldLoadUIForEnterForeground;
  109. BOOL isEnterForeground;
  110. BOOL startCheckBiometrics;
  111. UIView *lockView;
  112. }
  113. @synthesize window = _window;
  114. @synthesize urlRestoreData;
  115. @synthesize appLaunchDate;
  116. @synthesize isAppLocked;
  117. @synthesize isLockscreenDismissed;
  118. + (void)initialize {
  119. static dispatch_once_t onceToken;
  120. dispatch_once(&onceToken, ^{
  121. [AppGroup setGroupId:THREEMA_GROUP_IDENTIFIER];
  122. [AppGroup setAppId:APP_ID];
  123. #ifdef DEBUG
  124. [LogManager initializeGlobalLoggerWithDebug:YES];
  125. #else
  126. [LogManager initializeGlobalLoggerWithDebug:NO];
  127. #endif
  128. // Initialize app setup state (checking database file exists) as early as possible
  129. (void)[[AppSetupState alloc] init];
  130. });
  131. }
  132. + (AppDelegate*)sharedAppDelegate {
  133. __block AppDelegate *appDelegate = nil;
  134. if ([NSThread isMainThread]) {
  135. [AppDelegate initAppDelegate];
  136. appDelegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
  137. } else {
  138. dispatch_sync(dispatch_get_main_queue(), ^{
  139. [AppDelegate initAppDelegate];
  140. appDelegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
  141. });
  142. }
  143. return appDelegate;
  144. }
  145. + (void)initAppDelegate {
  146. [ActivityIndicatorProxy wireActivityIndicator:[SDNetworkActivityIndicator sharedActivityIndicator]];
  147. [MessageProcessorProxy wireMessageProcessor:[MessageProcessor sharedMessageProcessor]];
  148. [BackgroundTaskManagerProxy wireBackgroundTaskManager:[BackgroundTaskManager shared]];
  149. }
  150. #pragma mark - Launching
  151. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)_launchOptions
  152. {
  153. shouldLoadUIForEnterForeground = false;
  154. isEnterForeground = false;
  155. databaseImported = false;
  156. startCheckBiometrics = false;
  157. launchOptions = _launchOptions;
  158. isLockscreenDismissed = true;
  159. [ErrorNotificationHandler setup];
  160. appLaunchDate = [NSDate date];
  161. [NotificationManager sharedInstance];
  162. /* Instantiate various singletons now */
  163. [NaClCrypto sharedCrypto];
  164. [ServerConnector sharedServerConnector];
  165. [[PendingMessagesManager shared] setup];
  166. /* Observe connection status */
  167. [[ServerConnector sharedServerConnector] addObserver:self forKeyPath:@"connectionState" options:0 context:nil];
  168. self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
  169. pendingShortCutItem = [launchOptions objectForKey:UIApplicationLaunchOptionsShortcutItemKey];
  170. [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self];
  171. PKPushRegistry *pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
  172. pushRegistry.delegate = self;
  173. pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
  174. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleLicenseMissing:) name:kNotificationLicenseMissing object:nil];
  175. if ([[NSUserDefaults standardUserDefaults] boolForKey:@"FASTLANE_SNAPSHOT"]) {
  176. [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) {
  177. //
  178. }];
  179. }
  180. #ifdef DEBUG
  181. if ([[DatabaseManager dbManager] copyOldVersionOfDatabase]) {
  182. DDLogWarn(@"Old version of database would be applied. Start the app again for testing database migration! Caution, please check on devices that the App is not running (in background) anymore, otherwise migration will fail!!!");
  183. exit(EXIT_SUCCESS);
  184. }
  185. #endif
  186. // if database must migrate and app runs in background (e.g of push request), show local notification to start app in foreground
  187. DatabaseManager *dbManager = [DatabaseManager dbManager];
  188. if (([dbManager storeRequiresImport] || [dbManager storeRequiresMigration]) && [self isAppInBackground]) {
  189. [NotificationManager showNoAccessToDatabaseNotification];
  190. sleep(2);
  191. exit(EXIT_SUCCESS);
  192. }
  193. if ([dbManager storeRequiresImport]) {
  194. DDLogVerbose(@"Store will be import and start migration");
  195. migrating = YES;
  196. [self performSelectorOnMainThread:@selector(launchImportDatabase) withObject:nil waitUntilDone:NO];
  197. } else {
  198. if ([dbManager storeRequiresMigration]) {
  199. DDLogVerbose(@"Store requires migration");
  200. migrating = YES;
  201. /* run phase 2 (which involves migration) separately to avoid getting killed with "failed to launch in time" */
  202. [self performSelectorOnMainThread:@selector(launchPhase2) withObject:nil waitUntilDone:NO];
  203. } else {
  204. /* run phase 3 immediately */
  205. [self launchPhase3];
  206. }
  207. }
  208. if ([[NSUserDefaults standardUserDefaults] boolForKey:@"FASTLANE_SNAPSHOT"]) {
  209. NSString *theme = [[NSUserDefaults standardUserDefaults] objectForKey:@"theme"];
  210. if ([LicenseStore requiresLicenseKey] == YES) {
  211. if ([theme isEqualToString:@"dark"]) {
  212. [Colors setTheme:ColorThemeDarkWork];
  213. } else {
  214. [Colors setTheme:ColorThemeLightWork];
  215. }
  216. } else {
  217. if ([theme isEqualToString:@"dark"]) {
  218. [Colors setTheme:ColorThemeDark];
  219. } else {
  220. [Colors setTheme:ColorThemeLight];
  221. }
  222. }
  223. [[UserSettings sharedUserSettings] setEnableCallKit:false];
  224. }
  225. if (@available(iOS 13.0, *)) {
  226. if ([UserSettings sharedUserSettings].useSystemTheme == false && _window.overrideUserInterfaceStyle == UIUserInterfaceStyleUnspecified) {
  227. switch ([Colors getTheme]) {
  228. case ColorThemeDark:
  229. case ColorThemeDarkWork:
  230. _window.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
  231. break;
  232. default:
  233. _window.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
  234. break;
  235. }
  236. } else {
  237. if ([UserSettings sharedUserSettings].useSystemTheme == true && _window.overrideUserInterfaceStyle != UIUserInterfaceStyleUnspecified) {
  238. _window.overrideUserInterfaceStyle = UIUserInterfaceStyleUnspecified;
  239. }
  240. }
  241. }
  242. // Delete Threema-ID-Backup when backup is blocked from MDM
  243. MDMSetup *mdmSetup = [[MDMSetup alloc] initWithSetup:NO];
  244. if (mdmSetup.disableBackups || mdmSetup.disableIdExport || mdmSetup.disableSystemBackups) {
  245. [IdentityBackupStore deleteIdentityBackup];
  246. }
  247. // Prevent or release iOS iCloud backup
  248. [dbManager disableBackupForDatabaseDirectory:(mdmSetup.disableBackups || mdmSetup.disableSystemBackups)];
  249. [self registerMemoryWarningNotifications];
  250. return YES;
  251. }
  252. - (void)launchImportDatabase {
  253. UIImage *copyDatabaseBg;
  254. if ([UIScreen mainScreen].bounds.size.height > 480.0f)
  255. copyDatabaseBg = [UIImage imageNamed:@"migration_bg_2.jpg"];
  256. else
  257. copyDatabaseBg = [UIImage imageNamed:@"migration_bg_1.jpg"];
  258. UIImageView *copyDatabaseBgView = [[UIImageView alloc] initWithImage:copyDatabaseBg];
  259. copyDatabaseBgView.frame = self.window.frame;
  260. copyDatabaseBgView.contentMode = UIViewContentModeScaleAspectFill;
  261. [self.window addSubview:copyDatabaseBgView];
  262. [self.window makeKeyAndVisible];
  263. /* display spinner during copy database */
  264. [SVProgressHUD showWithStatus:NSLocalizedString(@"updating_database", nil)];
  265. /* run copy database now in background */
  266. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
  267. dispatch_async(dispatch_get_main_queue(), ^{
  268. [UIApplication sharedApplication].idleTimerDisabled = YES;
  269. });
  270. DatabaseManager *dbManager = [DatabaseManager dbManager];
  271. [dbManager copyImportedDatabase];
  272. NSError *storeError = [dbManager storeError];
  273. if (storeError != nil) {
  274. dispatch_async(dispatch_get_main_queue(), ^{
  275. [SVProgressHUD dismiss];
  276. [ErrorHandler abortWithError: storeError additionalText:@"Failed to import database"];
  277. /* do not run launchPhase3 at this point, as we shouldn't load the main storyboard and cause any database accesses */
  278. });
  279. } else {
  280. dispatch_async(dispatch_get_main_queue(), ^{
  281. if ([[DatabaseManager dbManager] storeRequiresMigration]) {
  282. DDLogVerbose(@"Store requires migration");
  283. databaseImported = YES;
  284. /* run phase 2 (which involves migration) separately to avoid getting killed with "failed to launch in time" */
  285. [self performSelectorOnMainThread:@selector(launchPhase2) withObject:nil waitUntilDone:NO];
  286. } else {
  287. [SVProgressHUD dismiss];
  288. migrating = NO;
  289. /* run phase 3 immediately */
  290. [self applicationDidBecomeActive:[UIApplication sharedApplication]];
  291. [UIApplication sharedApplication].idleTimerDisabled = NO;
  292. [self launchPhase3];
  293. }
  294. });
  295. }
  296. });
  297. }
  298. - (void)launchPhase2 {
  299. /* migration phase */
  300. if (databaseImported == false) {
  301. WizardBackgroundView *migrationBgView = [[WizardBackgroundView alloc] initWithFrame:self.window.frame];
  302. migrationBgView.contentMode = UIViewContentModeScaleAspectFill;
  303. [self.window addSubview:migrationBgView];
  304. [self.window makeKeyAndVisible];
  305. }
  306. /* check disk space first */
  307. if ([[DatabaseManager dbManager] canMigrateDB] == NO) {
  308. return;
  309. }
  310. if (databaseImported == false) {
  311. /* display spinner during migration */
  312. [SVProgressHUD showWithStatus:NSLocalizedString(@"updating_database", nil)];
  313. }
  314. /* run migration now in background */
  315. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
  316. dispatch_async(dispatch_get_main_queue(), ^{
  317. [UIApplication sharedApplication].idleTimerDisabled = YES;
  318. });
  319. DatabaseManager *dbManager = [DatabaseManager dbManager];
  320. [dbManager doMigrateDB];
  321. NSError *storeError = [dbManager storeError];
  322. if (storeError != nil) {
  323. dispatch_async(dispatch_get_main_queue(), ^{
  324. [SVProgressHUD dismiss];
  325. [ErrorHandler abortWithError: storeError additionalText:NSLocalizedString(@"database_migration_error_hints", nil)];
  326. /* do not run launchPhase3 at this point, as we shouldn't load the main storyboard and cause any database accesses */
  327. });
  328. } else {
  329. dispatch_async(dispatch_get_main_queue(), ^{
  330. [SVProgressHUD dismiss];
  331. [self launchPhase3];
  332. migrating = NO;
  333. [self applicationDidBecomeActive:[UIApplication sharedApplication]];
  334. [UIApplication sharedApplication].idleTimerDisabled = NO;
  335. });
  336. }
  337. });
  338. }
  339. - (void)launchPhase3 {
  340. /* Check that the store is OK */
  341. NSError *storeError = [[DatabaseManager dbManager] storeError];
  342. if (storeError != nil) {
  343. dispatch_async(dispatch_get_main_queue(), ^{
  344. [ErrorHandler abortWithError: storeError];
  345. return;
  346. });
  347. }
  348. // apply MDM parameter anyway, perhaps company MDM has changed
  349. MDMSetup *mdmSetup = [[MDMSetup alloc] initWithSetup:NO];
  350. [mdmSetup loadRenewableValues];
  351. [AppDelegate sharedAppDelegate];
  352. /* generate key pair and register with server if not existing */
  353. AppSetupState *appSetupState = [[AppSetupState alloc] initWithMyIdentityStore:[MyIdentityStore sharedMyIdentityStore]];
  354. if (![appSetupState isAppSetupCompleted]) {
  355. [self presentKeyGenerationOrProtectedDataUnavailable];
  356. [self.window makeKeyAndVisible];
  357. return;
  358. }
  359. if ([[DatabaseManager dbManager] shouldUpdateProtection]) {
  360. MyIdentityStore *myIdentityStore = [MyIdentityStore sharedMyIdentityStore];
  361. [myIdentityStore updateConnectionRights];
  362. [[DatabaseManager dbManager] updateProtection];
  363. }
  364. [[ContactStore sharedContactStore] updateAllContactsToCNContact];
  365. [ConversationUtils resetUnreadMessageCount];
  366. [NotificationManager generatePushSettingForAllGroups];
  367. [TypingIndicatorManager sharedInstance];
  368. [[KKPasscodeLock sharedLock] setDefaultSettings];
  369. [[KKPasscodeLock sharedLock] upgradeAccessibility];
  370. [KKPasscodeLock sharedLock].attemptsAllowed = 10;
  371. NSInteger state = [[VoIPCallStateManager shared] currentCallState];
  372. if ((state != CallStateIdle && state != CallStateSendOffer && state != CallStateReceivedOffer) | ![[KKPasscodeLock sharedLock] isPasscodeRequired]) {
  373. [self presentApplicationUI];
  374. } else {
  375. [self presentPasscodeView];
  376. }
  377. [self.window makeKeyAndVisible];
  378. toaster = [[NewMessageToaster alloc] init];
  379. if (shouldLoadUIForEnterForeground == false) {
  380. [self performSelectorOnMainThread:@selector(updateAllContacts) withObject:nil waitUntilDone:NO];
  381. }
  382. [self checkForInvalidCountryCode];
  383. [IdentityBackupStore syncKeychainWithFile];
  384. [self cleanInbox];
  385. }
  386. #pragma mark - Storyboards
  387. + (UIStoryboard *)getLaunchStoryboard {
  388. if ([LicenseStore requiresLicenseKey] == NO) {
  389. return [UIStoryboard storyboardWithName:@"ThreemaLaunchScreen" bundle:nil];
  390. } else {
  391. return [UIStoryboard storyboardWithName:@"ThreemaWorkLaunchScreen" bundle:nil];
  392. }
  393. }
  394. + (UIStoryboard *)getMainStoryboard {
  395. return [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];
  396. }
  397. + (UIStoryboard *)getSettingsStoryboard {
  398. return [UIStoryboard storyboardWithName:@"SettingsStoryboard" bundle:nil];
  399. }
  400. + (UIStoryboard *)getMyIdentityStoryboard {
  401. return [UIStoryboard storyboardWithName:@"MyIdentityStoryboard" bundle:nil];
  402. }
  403. + (UITabBarController *)getMainTabBarController {
  404. AppDelegate *appDelegate = [AppDelegate sharedAppDelegate];
  405. UITabBarController *mainTabBar;
  406. if (SYSTEM_IS_IPAD && [appDelegate.window.rootViewController isKindOfClass:[SplitViewController class]]) {
  407. SplitViewController *splitViewController = (SplitViewController*)appDelegate.window.rootViewController;
  408. mainTabBar = (UITabBarController*)splitViewController.viewControllers[1];
  409. } else {
  410. mainTabBar = (UITabBarController*)appDelegate.window.rootViewController;
  411. }
  412. if ([mainTabBar isKindOfClass:[UITabBarController class]]) {
  413. return mainTabBar;
  414. } else {
  415. return nil;
  416. }
  417. }
  418. #pragma mark - UI handling
  419. - (void)fixTabBarNotBeingHidden {
  420. // reselect chat bar in order to trigger hiding of tab bar again
  421. UITabBarController *mainTabBarController = [AppDelegate getMainTabBarController];
  422. if (mainTabBarController.selectedIndex == kChatTabBarIndex) {
  423. mainTabBarController.selectedIndex = kContactsTabBarIndex;
  424. mainTabBarController.selectedIndex = kChatTabBarIndex;
  425. }
  426. }
  427. - (void)completedIDSetup {
  428. NSInteger state = [[VoIPCallStateManager shared] currentCallState];
  429. if ((state != CallStateIdle && state != CallStateSendOffer && state != CallStateReceivedOffer) | ![[KKPasscodeLock sharedLock] isPasscodeRequired]) {
  430. [self presentApplicationUI];
  431. } else {
  432. [self presentPasscodeView];
  433. }
  434. toaster = [[NewMessageToaster alloc] init];
  435. }
  436. - (void)presentApplicationUI {
  437. [AppGroup setActive:NO forType:AppGroupTypeShareExtension];
  438. if ([self isAppInBackground] && isEnterForeground == false) {
  439. shouldLoadUIForEnterForeground = true;
  440. UIStoryboard *launchStoryboard = [AppDelegate getLaunchStoryboard];
  441. self.window.rootViewController = [launchStoryboard instantiateInitialViewController];
  442. } else {
  443. if (lastViewController != nil) {
  444. if (lockView != nil) {
  445. [lockView removeFromSuperview];
  446. }
  447. self.window.rootViewController = lastViewController;
  448. [self fixTabBarNotBeingHidden];
  449. lastViewController = nil;
  450. lockView = nil;
  451. } else if (SYSTEM_IS_IPAD) {
  452. SplitViewController *splitViewController = [[SplitViewController alloc] init];
  453. [splitViewController setup];
  454. self.window.rootViewController = splitViewController;
  455. } else {
  456. UIViewController *currentVC = self.window.rootViewController;
  457. if (currentVC != nil) {
  458. [currentVC dismissViewControllerAnimated:true completion:nil];
  459. }
  460. UIStoryboard *mainStoryboard = [AppDelegate getMainStoryboard];
  461. self.window.rootViewController = [mainStoryboard instantiateInitialViewController];
  462. }
  463. // Start crash report handler
  464. SentryClient *sentry = [[SentryClient alloc] init];
  465. [sentry startWithRootViewController:self.window.rootViewController];
  466. [self updateIdentityInfo];
  467. [[NotificationManager sharedInstance] updateUnreadMessagesCount:NO];
  468. [MessageQueue sharedMessageQueue];
  469. [AppDelegate setupConnection];
  470. /* Handle notification, if any */
  471. NSDictionary *remoteNotification = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
  472. if (remoteNotification != nil) {
  473. [self handleRemoteNotification:remoteNotification receivedWhileRunning:NO notification:nil];
  474. }
  475. if (shouldLoadUIForEnterForeground == false) {
  476. if ([[WCSessionManager shared] isRunningWCSession] == true){
  477. DDLogNotice(@"Threema Web: presentApplicationUI --> connect all running sessions");
  478. }
  479. [[WCSessionManager shared] connectAllRunningSessions];
  480. }
  481. /* A good chance to show reminders, if necessary */
  482. [[UserReminder sharedUserReminder] checkReminders:^(BOOL check) {
  483. if (![UserSettings sharedUserSettings].acceptedPrivacyPolicyDate) {
  484. [UserSettings sharedUserSettings].acceptedPrivacyPolicyDate = [NSDate date];
  485. [UserSettings sharedUserSettings].acceptedPrivacyPolicyVariant = AcceptPrivacyPolicyVariantUpdate;
  486. }
  487. if ([[UserSettings sharedUserSettings] openPlusIconInChat] == YES) {
  488. [[UserSettings sharedUserSettings] setOpenPlusIconInChat:NO];
  489. [[UserSettings sharedUserSettings] setShowGalleryPreview:NO];
  490. }
  491. dispatch_async(dispatch_get_main_queue(), ^{
  492. [[UIApplication sharedApplication] unregisterForRemoteNotifications];
  493. });
  494. [self handlePresentingScreens];
  495. shouldLoadUIForEnterForeground = false;
  496. }];
  497. }
  498. }
  499. - (void)handlePresentingScreens {
  500. //shouldLoadUIForEnterForeground == false: means UI was never loaded before
  501. if (shouldLoadUIForEnterForeground == false) {
  502. if (![[VoIPHelper shared] isCallActiveInBackground] && [[VoIPCallStateManager shared] currentCallState] != CallStateIdle) {
  503. if ([lastViewController.presentedViewController isKindOfClass:[CallViewController class]] || SYSTEM_IS_IPAD) {
  504. dispatch_async(dispatch_get_main_queue(), ^{
  505. [self.window.rootViewController dismissViewControllerAnimated:NO completion:nil];
  506. });
  507. }
  508. } else {
  509. // show Threema Safe Intro once after App update
  510. if ([[NSUserDefaults standardUserDefaults] boolForKey:@"FASTLANE_SNAPSHOT"]) {
  511. // do not show threema safe intro
  512. } else {
  513. // Check if backup is forced from MDM
  514. SafeConfigManager *safeConfigManager = [[SafeConfigManager alloc] init];
  515. SafeStore *safeStore = [[SafeStore alloc] initWithSafeConfigManagerAsObject:safeConfigManager serverApiConnector:[[ServerAPIConnector alloc] init]];
  516. SafeManager *safeManager = [[SafeManager alloc] initWithSafeConfigManagerAsObject:safeConfigManager safeStore:safeStore safeApiService:[[SafeApiService alloc] init]];
  517. MDMSetup *mdmSetup = [[MDMSetup alloc] initWithSetup:NO];
  518. // check if Threema Safe is forced and not activated yet
  519. if (![safeManager isActivated] && [mdmSetup isSafeBackupForce]) {
  520. if ([mdmSetup safePassword] == nil) {
  521. // show password without cancel button for Threema Safe
  522. dispatch_async(dispatch_get_main_queue(), ^{
  523. UIViewController *vc = [UIApplication sharedApplication].keyWindow.rootViewController;
  524. UIStoryboard *storyboard = [AppDelegate getMyIdentityStoryboard];
  525. UINavigationController *safeSetupNavigationViewController = [storyboard instantiateViewControllerWithIdentifier:@"SafeIntroNavigationController"];
  526. SafeSetupPasswordViewController *safeSetupPasswordViewController = (SafeSetupPasswordViewController*)[safeSetupNavigationViewController topViewController];
  527. safeSetupPasswordViewController.isForcedBackup = true;
  528. [vc presentViewController:safeSetupNavigationViewController animated:YES completion:nil];
  529. });
  530. } else {
  531. // activate with the password of the MDM
  532. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  533. // or if password is already set from MDM (automaticly perform safe)
  534. NSString *customServer = nil;
  535. NSString *server = nil;
  536. if ([mdmSetup isSafeBackupServerPreset]) {
  537. // server is given by MDM
  538. customServer = [mdmSetup safeServerUrl];
  539. server = [safeStore composeSafeServerAuthWithServer:[mdmSetup safeServerUrl] user:[mdmSetup safeServerUsername] password:[mdmSetup safeServerPassword]].absoluteString;
  540. }
  541. NSError *error;
  542. [safeManager activateWithIdentity:[MyIdentityStore sharedMyIdentityStore].identity password:[mdmSetup safePassword] customServer:customServer server:server maxBackupBytes:nil retentionDays:nil error:&error];
  543. });
  544. }
  545. }
  546. // if Threema Safe is disabled by MDM and Safe is activated, deactivate Safe
  547. else if ([safeManager isActivated] && [mdmSetup isSafeBackupDisable]) {
  548. [safeManager deactivate];
  549. }
  550. // if Safe activated, check if server has been changed by MDM
  551. else if ([safeManager isActivated] && [mdmSetup isManaged]) {
  552. if ([mdmSetup isSafeBackupServerPreset]) {
  553. [safeManager applyServerWithServer:[mdmSetup safeServerUrl] username:[mdmSetup safeServerUsername] password:[mdmSetup safeServerPassword]];
  554. } else {
  555. [safeManager applyServerWithServer:nil username:nil password:nil];
  556. }
  557. }
  558. else {
  559. // if Safe not activated and not disabled by MDM, show intro if is necessary
  560. if ([[UserSettings sharedUserSettings] safeIntroShown] == NO && ![safeManager isActivated] && ![mdmSetup isSafeBackupDisable]) {
  561. [[UserSettings sharedUserSettings] setSafeIntroShown:YES];
  562. if (![safeManager isActivated]) {
  563. dispatch_async(dispatch_get_main_queue(), ^{
  564. UIViewController *vc = [UIApplication sharedApplication].keyWindow.rootViewController;
  565. UIStoryboard *storyboard = [AppDelegate getMyIdentityStoryboard];
  566. UIViewController *safeIntroViewController = [storyboard instantiateViewControllerWithIdentifier:@"SafeIntroViewController"];
  567. [vc presentViewController:safeIntroViewController animated:YES completion:nil];
  568. });
  569. }
  570. }
  571. }
  572. }
  573. }
  574. }
  575. }
  576. - (UIViewController *)currentTopViewController {
  577. UIViewController *topVC = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
  578. while (topVC.presentedViewController) {
  579. topVC = topVC.presentedViewController;
  580. }
  581. return topVC;
  582. }
  583. + (UIAlertController *)isAlertViewShown {
  584. UIViewController *vc = [[[AppDelegate sharedAppDelegate] window] rootViewController];
  585. if ([vc.presentedViewController isKindOfClass:[UIAlertController class]]) {
  586. return (UIAlertController *)vc.presentedViewController;
  587. }
  588. return nil;
  589. }
  590. - (BOOL)isPresentingKeyGeneration {
  591. UIViewController *presentedVC = [self presentedCurrentViewController];
  592. if ([presentedVC isKindOfClass:[SplashViewController class]] || [presentedVC isKindOfClass:[RestoreOptionDataViewController class]] || [presentedVC isKindOfClass:[RestoreOptionBackupViewController class]] || [presentedVC isKindOfClass:[RestoreIdentityViewController class]]) {
  593. return YES;
  594. }
  595. return NO;
  596. }
  597. - (BOOL)isPresentingEnterLicense {
  598. __block UIViewController *presentedVC;
  599. if ([NSThread isMainThread]) {
  600. presentedVC = self.window.rootViewController.presentedViewController;
  601. } else {
  602. dispatch_sync(dispatch_get_main_queue(), ^{
  603. presentedVC = self.window.rootViewController.presentedViewController;
  604. });
  605. }
  606. if ([presentedVC isKindOfClass:[EnterLicenseViewController class]]) {
  607. return YES;
  608. }
  609. return NO;
  610. }
  611. - (void)presentKeyGeneration {
  612. [self.window.rootViewController dismissViewControllerAnimated:NO completion:nil];
  613. UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"CreateID" bundle:[NSBundle mainBundle] ];
  614. UIViewController *createIdVC = [storyboard instantiateInitialViewController];
  615. self.window.rootViewController = createIdVC;
  616. }
  617. - (void)presentProtectedDataUnavailable {
  618. [self.window.rootViewController dismissViewControllerAnimated:NO completion:nil];
  619. UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"ProtectedDataUnavailable" bundle:[NSBundle mainBundle] ];
  620. UIViewController *unavailableVC = [storyboard instantiateInitialViewController];
  621. self.window.rootViewController = unavailableVC;
  622. }
  623. - (void)presentIDBackupRestore {
  624. UIViewController *presentedVC = [self presentedCurrentViewController];
  625. if ([presentedVC isKindOfClass:[SplashViewController class]]) {
  626. [((SplashViewController *)presentedVC) showRestoreIdentityViewController];
  627. } else if ([presentedVC isKindOfClass:[RestoreOptionBackupViewController class]]) {
  628. [((SplashViewController *)[((RestoreOptionBackupViewController *)presentedVC) parentViewController]) showRestoreIdentityViewController];
  629. } else if ([presentedVC isKindOfClass:[RestoreIdentityViewController class]]) {
  630. [((RestoreIdentityViewController *)presentedVC) updateTextViewWithBackupCode];
  631. }
  632. }
  633. - (void)presentKeyGenerationOrProtectedDataUnavailable {
  634. if ([[MyIdentityStore sharedMyIdentityStore] isKeychainLocked]) {
  635. [self presentProtectedDataUnavailable];
  636. } else {
  637. [WorkDataFetcher checkUpdateThreemaMDM:^{
  638. } onError:^(NSError *error) {
  639. }];
  640. [self presentKeyGeneration];
  641. }
  642. }
  643. - (void)askForPushDecryption {
  644. if (![UserSettings sharedUserSettings].askedForPushDecryption) {
  645. MDMSetup *mdmSetup = [[MDMSetup alloc] initWithSetup:NO];
  646. if ([mdmSetup existsMdmKey:MDM_KEY_DISABLE_MESSAGE_PREVIEW]) {
  647. [[UserSettings sharedUserSettings] setPushDecrypt:NO];
  648. } else {
  649. UIAlertController * alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"decryption_push_title", nil) message:NSLocalizedString(@"decryption_push_text", nil) preferredStyle:UIAlertControllerStyleAlert];
  650. UIAlertAction* yesButton = [UIAlertAction actionWithTitle:NSLocalizedString(@"decryption_push_activate", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
  651. [[UserSettings sharedUserSettings] setPushDecrypt:YES];
  652. [[UserSettings sharedUserSettings] setAskedForPushDecryption:YES];
  653. }];
  654. UIAlertAction* noButton = [UIAlertAction actionWithTitle:NSLocalizedString(@"decryption_push_deactivate", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
  655. [[UserSettings sharedUserSettings] setPushDecrypt:NO];
  656. [[UserSettings sharedUserSettings] setAskedForPushDecryption:YES];
  657. }];
  658. [alert addAction:yesButton];
  659. [alert addAction:noButton];
  660. [_window.rootViewController presentViewController:alert animated:YES completion:nil];
  661. }
  662. }
  663. }
  664. - (UIViewController *)presentedCurrentViewController {
  665. UIViewController *presentedVC = self.window.rootViewController.presentedViewController;
  666. if (presentedVC == nil && [self.window.rootViewController.childViewControllers count] > 0) {
  667. presentedVC = self.window.rootViewController.childViewControllers[0];
  668. } else if (presentedVC == nil) {
  669. presentedVC = self.window.rootViewController;
  670. }
  671. return presentedVC;
  672. }
  673. - (void)handleLicenseMissing:(NSNotification*)notification {
  674. if ([LicenseStore requiresLicenseKey] == NO) {
  675. return;
  676. }
  677. UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"License" bundle:nil];
  678. EnterLicenseViewController *viewController = [storyboard instantiateInitialViewController];
  679. dispatch_async(dispatch_get_main_queue(), ^{
  680. if ([self.window.rootViewController isKindOfClass:[viewController class]] == NO) {
  681. viewController.delegate = self;
  682. [[WCSessionManager shared] stopAndForgetAllSessions];
  683. [self.window.rootViewController presentViewController:viewController animated:NO completion:nil];
  684. }
  685. });
  686. }
  687. - (void)showLockScreen {
  688. /* replace the root view controller to ensure it's not visible in snapshots */
  689. if ([[KKPasscodeLock sharedLock] isPasscodeRequired]) {
  690. if (![lockView isDescendantOfView:self.window]) {
  691. if ([self.window.rootViewController isKindOfClass:[UINavigationController class]]) {
  692. UINavigationController *nav = (UINavigationController *)self.window.rootViewController;
  693. if (![[[nav childViewControllers] objectAtIndex:0] isKindOfClass:[JKLLockScreenViewController class]]) {
  694. lastViewController = self.window.rootViewController;
  695. }
  696. } else {
  697. if (![self.window.rootViewController isKindOfClass:[JKLLockScreenViewController class]]) {
  698. lastViewController = self.window.rootViewController;
  699. }
  700. }
  701. [lastViewController.view endEditing:YES];
  702. [lastViewController.presentedViewController.view endEditing:YES];
  703. if ([lastViewController.presentedViewController isKindOfClass:[CallViewController class]]) {
  704. [lastViewController dismissViewControllerAnimated:NO completion:nil];
  705. }
  706. UIViewController *lockCover = nil;
  707. if ([LicenseStore requiresLicenseKey] == YES) {
  708. lockCover = [[UIViewController alloc] initWithNibName:@"LockCoverWork" bundle:nil];
  709. } else {
  710. lockCover = [[UIViewController alloc] initWithNibName:@"LockCover" bundle:nil];
  711. }
  712. lockView = lockCover.view;
  713. lockView.frame = self.window.bounds;
  714. [self.window insertSubview:lockView atIndex:99999];
  715. [self.window bringSubviewToFront:lockView];
  716. [self.window snapshotViewAfterScreenUpdates:false];
  717. isLockscreenDismissed = false;
  718. } else {
  719. [self.window bringSubviewToFront:lockView];
  720. [self.window snapshotViewAfterScreenUpdates:false];
  721. }
  722. }
  723. }
  724. #pragma mark - Server Connection and Notifications
  725. + (void)setupConnection {
  726. // Add received pushes into DB
  727. if (![[AppDelegate sharedAppDelegate] isAppInBackground]) {
  728. [[ServerConnector sharedServerConnector] connect];
  729. }
  730. [FeatureMask updateFeatureMask];
  731. [AppDelegate registerForLocalNotifications];
  732. }
  733. + (void)registerForLocalNotifications {
  734. UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
  735. [center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error)
  736. {
  737. if( !error ) {
  738. dispatch_async(dispatch_get_main_queue(), ^{
  739. [[UIApplication sharedApplication] registerForRemoteNotifications];
  740. });
  741. // required to get the app to do anything at all about push notifications
  742. UNTextInputNotificationAction *textAction = [UNTextInputNotificationAction actionWithIdentifier:@"REPLY_MESSAGE" title:NSLocalizedString(@"decryption_push_reply", nil) options:UNNotificationActionOptionAuthenticationRequired textInputButtonTitle:NSLocalizedString(@"send", nil) textInputPlaceholder:NSLocalizedString(@"decryption_push_placeholder", nil)];
  743. UNNotificationAction *thumbUpAction = [UNNotificationAction actionWithIdentifier:@"THUMB_UP" title:NSLocalizedString(@"decryption_push_agree", nil) options:UNNotificationActionOptionAuthenticationRequired];
  744. UNNotificationAction *thumbDownAction = [UNNotificationAction actionWithIdentifier:@"THUMB_DOWN" title:NSLocalizedString(@"decryption_push_disagree", nil) options:UNNotificationActionOptionAuthenticationRequired];
  745. UNTextInputNotificationAction *callReplyAction = [UNTextInputNotificationAction actionWithIdentifier:@"REPLY_MESSAGE" title:NSLocalizedString(@"decryption_push_reply", nil) options:UNNotificationActionOptionAuthenticationRequired textInputButtonTitle:NSLocalizedString(@"send", nil) textInputPlaceholder:NSLocalizedString(@"decryption_push_placeholder", nil)];
  746. UNNotificationAction *callBackAction = [UNNotificationAction actionWithIdentifier:@"CALL" title:NSLocalizedString(@"call_back", nil) options:UNNotificationActionOptionForeground];
  747. UNNotificationAction *acceptCallAction = [UNNotificationAction actionWithIdentifier:@"ACCEPTCALL" title:NSLocalizedString(@"call_accept", nil) options:UNNotificationActionOptionForeground];
  748. UNNotificationAction *rejectCallAction = [UNNotificationAction actionWithIdentifier:@"REJECTCALL" title:NSLocalizedString(@"call_reject", nil) options:UNNotificationActionOptionDestructive];
  749. // Create the category with the custom actions.
  750. UNNotificationCategory *singleCategory = [UNNotificationCategory categoryWithIdentifier:@"SINGLE" actions:@[textAction, thumbUpAction, thumbDownAction] intentIdentifiers:@[] options:UNNotificationCategoryOptionNone];
  751. UNNotificationCategory *groupCategory = [UNNotificationCategory categoryWithIdentifier:@"GROUP" actions:@[textAction] intentIdentifiers:@[] options:UNNotificationCategoryOptionNone];
  752. UNNotificationCategory *callCategory = [UNNotificationCategory categoryWithIdentifier:@"CALL" actions:@[callReplyAction, callBackAction] intentIdentifiers:@[] options:UNNotificationCategoryOptionNone];
  753. UNNotificationCategory *incomCallCategory = [UNNotificationCategory categoryWithIdentifier:@"INCOMCALL" actions:@[acceptCallAction, rejectCallAction] intentIdentifiers:@[] options:UNNotificationCategoryOptionNone];
  754. // Register the notification categories.
  755. [center setNotificationCategories:[NSSet setWithObjects:groupCategory, singleCategory, callCategory, incomCallCategory, nil]];
  756. }
  757. }];
  758. }
  759. #pragma mark - Misc
  760. - (BOOL)isAppInBackground {
  761. __block BOOL inBackground = false;
  762. if ([NSThread isMainThread]) {
  763. inBackground = [[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground;
  764. } else {
  765. dispatch_sync(dispatch_get_main_queue(), ^{
  766. inBackground = [[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground;
  767. });
  768. }
  769. return inBackground;
  770. }
  771. - (void)checkForInvalidCountryCode {
  772. if ([PhoneNumberNormalizer userRegion] != nil)
  773. return;
  774. [UIAlertTemplate showAlertWithOwner:[self currentTopViewController] title:NSLocalizedString(@"invalid_country_code_title", nil) message:NSLocalizedString(@"invalid_country_code_message", nil) actionOk:nil];
  775. }
  776. - (void)updateIdentityInfo {
  777. MyIdentityStore *identityStore = [MyIdentityStore sharedMyIdentityStore];
  778. /* We have an identity, but is the (user defaults based) private identity info current?
  779. If the user deletes and reinstalls the app, the keychain data will be maintained, but
  780. the user defaults will be missing */
  781. if (identityStore.identity != nil && identityStore.privateIdentityInfoLastUpdate == nil) {
  782. DDLogInfo(@"Missing private identity info; fetching from server");
  783. ServerAPIConnector *apiConnector = [[ServerAPIConnector alloc] init];
  784. [apiConnector updateMyIdentityStore:identityStore onCompletion:^{
  785. identityStore.privateIdentityInfoLastUpdate = [NSDate date];
  786. } onError:^(NSError *error) {
  787. DDLogError(@"Private identity info update failed: %@", error);
  788. }];
  789. }
  790. }
  791. /**
  792. Selector method to process update all contacts in backgropund thread, to prevent blocking the app, otherwise will killed by watchdog.
  793. */
  794. - (void)updateAllContacts {
  795. [[ContactStore sharedContactStore] updateAllContacts];
  796. }
  797. - (void)cleanPushDirectory {
  798. // clean directory with push images
  799. NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
  800. NSString *path = [NSString stringWithFormat:@"%@/PushImages", cache];
  801. NSError *error;
  802. [[NSFileManager defaultManager] removeItemAtPath:path error:&error];
  803. }
  804. - (void)cleanInbox {
  805. // Clean Documents/Inbox directory (old files may accumulate there in case of aborted document share operations),
  806. // but only if we haven't been opened to share a URL
  807. if (launchOptions[UIApplicationLaunchOptionsURLKey] == nil) {
  808. [self removeDirectoryContents:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"Inbox"]];
  809. }
  810. }
  811. - (void)removeDirectoryContents:(NSString*)directory {
  812. DDLogInfo(@"Remove contents of %@", directory);
  813. NSFileManager *fm = [NSFileManager defaultManager];
  814. for (NSString *file in [fm contentsOfDirectoryAtPath:directory error:nil]) {
  815. [fm removeItemAtPath:[directory stringByAppendingPathComponent:file] error:nil];
  816. }
  817. }
  818. -(void)registerMemoryWarningNotifications {
  819. [[NSNotificationCenter defaultCenter] addObserverForName:
  820. UIApplicationDidReceiveMemoryWarningNotification
  821. object:[UIApplication sharedApplication] queue:nil
  822. usingBlock:^(__unused NSNotification *notif) {
  823. DDLogWarn(@"Received Memory Warning");
  824. }];
  825. }
  826. #pragma mark - Application delegates
  827. - (void)applicationWillResignActive:(UIApplication *)application {
  828. /*
  829. Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
  830. Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
  831. */
  832. DDLogInfo(@"applicationWillResignActive");
  833. self.active = NO;
  834. dispatch_async(dispatch_get_main_queue(), ^{
  835. self.firstPushHandled = false;
  836. });
  837. AppSetupState *appSetupState = [[AppSetupState alloc] initWithMyIdentityStore:[MyIdentityStore sharedMyIdentityStore]];
  838. if (![appSetupState isAppSetupCompleted]) {
  839. return;
  840. }
  841. [self showLockScreen];
  842. }
  843. - (void)applicationDidEnterBackground:(UIApplication *)application
  844. {
  845. /*
  846. Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
  847. If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
  848. */
  849. DDLogInfo(@"applicationDidEnterBackground");
  850. AppSetupState *appSetupState = [[AppSetupState alloc] initWithMyIdentityStore:[MyIdentityStore sharedMyIdentityStore]];
  851. if (![appSetupState isAppSetupCompleted]) {
  852. return;
  853. }
  854. [self showLockScreen];
  855. if (!isAppLocked) {
  856. [[KKPasscodeLock sharedLock] updateLastUnlockTime];
  857. /* replace the root view controller to ensure it's not visible in snapshots */
  858. if ([[KKPasscodeLock sharedLock] isPasscodeRequired]) {
  859. isAppLocked = YES;
  860. }
  861. } else {
  862. NSInteger state = [[VoIPCallStateManager shared] currentCallState];
  863. if (isAppLocked && (state != CallStateIdle && state != CallStateSendOffer && state != CallStateReceivedOffer)) {
  864. if ([[KKPasscodeLock sharedLock] isPasscodeRequired]) {
  865. isAppLocked = YES;
  866. }
  867. }
  868. }
  869. /* Is protected data still available? If not, then there's no point in starting a background task */
  870. if (!protectedDataWillBecomeUnavailable) {
  871. NSString *key = nil;
  872. int timeout = 0;
  873. if ([[WCSessionManager shared] isRunningWCSession] != true) {
  874. key = kAppClosedByUserBackgroundTask;
  875. timeout = kAppClosedByUserBackgroundTaskTime;
  876. } else {
  877. key = kAppWCBackgroundTask;
  878. timeout = kAppWCBackgroundTaskTime;
  879. }
  880. [[BackgroundTaskManager shared] newBackgroundTaskWithKey:key timeout:timeout completionHandler:^{
  881. [[MessageQueue sharedMessageQueue] save];
  882. [[PendingMessagesManager shared] save];
  883. [[WCSessionManager shared] saveSessionsToArchive];
  884. }];
  885. } else {
  886. /* Disconnect from server - from now on we want push notifications for new messages */
  887. [[ServerConnector sharedServerConnector] disconnectWait];
  888. [[MessageQueue sharedMessageQueue] save];
  889. [[WCSessionManager shared] saveSessionsToArchive];
  890. [[PendingMessagesManager shared] save];
  891. }
  892. }
  893. - (void)applicationWillEnterForeground:(UIApplication *)application
  894. {
  895. /*
  896. Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
  897. */
  898. DDLogInfo(@"applicationWillEnterForeground");
  899. AppSetupState *appSetupState = [[AppSetupState alloc] initWithMyIdentityStore:[MyIdentityStore sharedMyIdentityStore]];
  900. if (![appSetupState isAppSetupCompleted]) {
  901. return;
  902. }
  903. [[BackgroundTaskManager shared] cancelBackgroundTaskWithKey:kAppClosedByUserBackgroundTask];
  904. [[BackgroundTaskManager shared] cancelBackgroundTaskWithKey:kAppWCBackgroundTask];
  905. isEnterForeground = true;
  906. BOOL shouldLoadUI = true;
  907. if ([[KKPasscodeLock sharedLock] isPasscodeRequired] && [[VoIPHelper shared] isCallActiveInBackground]) {
  908. [self presentPasscodeView];
  909. shouldLoadUI = false;
  910. }
  911. else if ([[KKPasscodeLock sharedLock] isPasscodeRequired] && [[VoIPCallStateManager shared] currentCallState] != CallStateIncomingRinging && [[VoIPCallStateManager shared] currentCallState] != CallStateOutgoingRinging && [[VoIPCallStateManager shared] currentCallState] != CallStateInitalizing && [[VoIPCallStateManager shared] currentCallState] != CallStateCalling && [[VoIPCallStateManager shared] currentCallState] != CallStateReconnecting) {
  912. [self presentPasscodeView];
  913. shouldLoadUI = false;
  914. }
  915. else {
  916. if ([[KKPasscodeLock sharedLock] isPasscodeRequired] && ([[VoIPCallStateManager shared] currentCallState] == CallStateIncomingRinging || [[VoIPCallStateManager shared] currentCallState] == CallStateOutgoingRinging || [[VoIPCallStateManager shared] currentCallState] == CallStateInitalizing || [[VoIPCallStateManager shared] currentCallState] == CallStateCalling || [[VoIPCallStateManager shared] currentCallState] == CallStateReconnecting)) {
  917. if ([lastViewController.presentedViewController isKindOfClass:[CallViewController class]] || SYSTEM_IS_IPAD) {
  918. [self.window.rootViewController dismissViewControllerAnimated:NO completion:nil];
  919. [self presentApplicationUI];
  920. shouldLoadUI = false;
  921. }
  922. if (SYSTEM_IS_IPAD) {
  923. [[VoIPCallStateManager shared] presentCallViewController];
  924. shouldLoadUI = false;
  925. }
  926. }
  927. }
  928. if (shouldLoadUIForEnterForeground == true && shouldLoadUI == true) {
  929. [self performSelectorOnMainThread:@selector(presentApplicationUI) withObject:nil waitUntilDone:YES];
  930. [self performSelectorOnMainThread:@selector(updateAllContacts) withObject:nil waitUntilDone:NO];
  931. }
  932. [[DatabaseManager dbManager] refreshDirtyObjects];
  933. /* ensure we're connected when we enter into foreground */
  934. [AppGroup setActive:NO forType:AppGroupTypeShareExtension];
  935. if ([[WCSessionManager shared] isRunningWCSession] == true){
  936. [[ValidationLogger sharedValidationLogger] logString:@"Threema Web: applicationWillEnterForeground --> connect all running sessions"];
  937. }
  938. [[WCSessionManager shared] connectAllRunningSessions];
  939. [[ServerConnector sharedServerConnector] connect];
  940. [[TypingIndicatorManager sharedInstance] resetTypingIndicators];
  941. }
  942. - (void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application {
  943. DDLogInfo(@"applicationProtectedDataWillBecomeUnavailable");
  944. protectedDataWillBecomeUnavailable = YES;
  945. if ([[WCSessionManager shared] isRunningWCSession] == false) {
  946. [[BackgroundTaskManager shared] cancelBackgroundTaskWithKey:kAppClosedByUserBackgroundTask];
  947. }
  948. }
  949. - (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application {
  950. DDLogInfo(@"applicationProtectedDataDidBecomeAvailable");
  951. protectedDataWillBecomeUnavailable = NO;
  952. }
  953. - (void)applicationDidBecomeActive:(UIApplication *)application
  954. {
  955. /*
  956. Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
  957. */
  958. DDLogInfo(@"applicationDidBecomeActive");
  959. if (migrating) {
  960. return;
  961. }
  962. if ([[KKPasscodeLock sharedLock] isPasscodeRequired] && isAppLocked) {
  963. if (![self isPassCodeViewControllerPresented]) {
  964. [self presentPasscodeView];
  965. } else {
  966. if (lockView != nil) {
  967. [lockView removeFromSuperview];
  968. }
  969. }
  970. } else {
  971. if (lockView != nil) {
  972. [self performSelectorOnMainThread:@selector(presentApplicationUI) withObject:nil waitUntilDone:YES];
  973. }
  974. }
  975. self.active = YES;
  976. [AppGroup setActive:NO forType:AppGroupTypeShareExtension];
  977. AppSetupState *appSetupState = [[AppSetupState alloc] initWithMyIdentityStore:[MyIdentityStore sharedMyIdentityStore]];
  978. if (![appSetupState isAppSetupCompleted]) {
  979. return;
  980. }
  981. [[NotificationManager sharedInstance] updateUnreadMessagesCount:NO];
  982. // Remove notifications from center
  983. UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
  984. [center removeAllDeliveredNotifications];
  985. if ([[NSUserDefaults standardUserDefaults] boolForKey:@"FASTLANE_SNAPSHOT"]) {
  986. NSMutableOrderedSet *workIdentities = [NSMutableOrderedSet new];
  987. if ([LicenseStore requiresLicenseKey] == YES) {
  988. [workIdentities addObject:@"H3BK2FVH"];
  989. [workIdentities addObject:@"JYNBZX53"];
  990. [workIdentities addObject:@"RFH4BE5C"];
  991. }
  992. [workIdentities addObject:@"J3KK7X69"];
  993. [UserSettings sharedUserSettings].workIdentities = workIdentities;
  994. } else {
  995. [[ContactStore sharedContactStore] synchronizeAddressBookForceFullSync:NO onCompletion:nil onError:nil];
  996. }
  997. [WorkDataFetcher checkUpdateWorkDataForce:NO onCompletion:nil onError:nil];
  998. [[GatewayAvatarMaker gatewayAvatarMaker] refresh];
  999. if ([[VoIPCallStateManager shared] currentCallState] != CallStateIdle && ![VoIPHelper shared].isCallActiveInBackground) {
  1000. [[VoIPCallStateManager shared] presentCallViewController];
  1001. } else {
  1002. // if not a call, then trigger Threema Safe backup (it will show an alert here, if last successful backup older than 7 days)
  1003. SafeConfigManager *safeConfigManager = [[SafeConfigManager alloc] init];
  1004. SafeStore *safeStore = [[SafeStore alloc] initWithSafeConfigManagerAsObject:safeConfigManager serverApiConnector:[[ServerAPIConnector alloc] init]];
  1005. SafeManager *safeManager = [[SafeManager alloc] initWithSafeConfigManagerAsObject:safeConfigManager safeStore:safeStore safeApiService:[[SafeApiService alloc] init]];
  1006. [safeManager initTrigger];
  1007. }
  1008. [self cleanPushDirectory];
  1009. }
  1010. - (void)applicationWillTerminate:(UIApplication *)application
  1011. {
  1012. /*
  1013. Called when the application is about to terminate.
  1014. Save data if appropriate.
  1015. See also applicationDidEnterBackground:.
  1016. */
  1017. DDLogInfo(@"applicationWillTerminate");
  1018. [[MessageQueue sharedMessageQueue] save];
  1019. [[PendingMessagesManager shared] save];
  1020. [[WCSessionManager shared] saveSessionsToArchive];
  1021. }
  1022. #pragma mark - Observer
  1023. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  1024. if (object == [ServerConnector sharedServerConnector] && [keyPath isEqualToString:@"connectionState"]) {
  1025. DDLogInfo(@"Server connection state changed to %@", [[ServerConnector sharedServerConnector] nameForConnectionState:[ServerConnector sharedServerConnector].connectionState]);
  1026. [[ValidationLogger sharedValidationLogger] logString:[NSString stringWithFormat:@"Server connection state changed to %@", [[ServerConnector sharedServerConnector] nameForConnectionState:[ServerConnector sharedServerConnector].connectionState]]];
  1027. }
  1028. }
  1029. #pragma mark - Audio call intent
  1030. - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *restorationHandler))restorationHandler {
  1031. if ([userActivity.activityType isEqualToString:@"INStartAudioCallIntent"]) {
  1032. return [self handleStartAudioCallIntent:userActivity];
  1033. } else if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
  1034. [URLHandler handleURL:userActivity.webpageURL];
  1035. }
  1036. return NO;
  1037. }
  1038. - (BOOL)handleStartAudioCallIntent:(NSUserActivity*)userActivity {
  1039. INInteraction *interaction = userActivity.interaction;
  1040. INStartAudioCallIntent *startAudioCallIntent = (INStartAudioCallIntent *)interaction.intent;
  1041. INPerson *person = startAudioCallIntent.contacts[0];
  1042. INPersonHandle *personHandle = person.personHandle;
  1043. if (personHandle.value) {
  1044. EntityManager *entityManager = [[EntityManager alloc] init];
  1045. Contact *contact = [entityManager.entityFetcher contactForId:personHandle.value];
  1046. if (contact) {
  1047. [FeatureMask checkFeatureMask:FEATURE_MASK_VOIP forContacts:[NSSet setWithObjects:contact, nil] onCompletion:^(NSArray *unsupportedContacts) {
  1048. if (unsupportedContacts.count == 0) {
  1049. VoIPCallUserAction *action = [[VoIPCallUserAction alloc] initWithAction:ActionCall contact:contact callId:nil completion:nil];
  1050. [[VoIPCallStateManager shared] processUserAction:action];
  1051. } else {
  1052. [UIAlertTemplate showAlertWithOwner:[self currentTopViewController] title:NSLocalizedString(@"call_voip_not_supported_title", nil) message:NSLocalizedString(@"call_voip_not_supported_text", nil) actionOk:nil];
  1053. }
  1054. }];
  1055. }
  1056. }
  1057. return YES;
  1058. }
  1059. + (BOOL)hasBottomSafeAreaInsets {
  1060. if (@available(iOS 11.0, *)) {
  1061. return [[[[UIApplication sharedApplication] delegate] window] safeAreaInsets].bottom > 0;
  1062. } else {
  1063. return false;
  1064. }
  1065. }
  1066. #pragma mark - Push notifications
  1067. - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
  1068. DDLogWarn(@"Push registration failed: %@", error);
  1069. }
  1070. - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  1071. [self askForPushDecryption];
  1072. }
  1073. - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler {
  1074. [self handleRemoteNotification:notification.request.content.userInfo receivedWhileRunning:true notification:notification];
  1075. }
  1076. - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler {
  1077. // Decrypt Threema payload if necessary
  1078. NotificationResponse *notificationResponse = [[NotificationResponse alloc] initWithResponse:response completion:completionHandler];
  1079. [notificationResponse handleNotificationResponse];
  1080. }
  1081. - (void)handleRemoteNotification:(NSDictionary*)userInfo receivedWhileRunning:(BOOL)receivedWhileRunning notification:(UNNotification *)notification {
  1082. DDLogVerbose(@"Remote notification: %@, while running: %d", userInfo, receivedWhileRunning);
  1083. AppSetupState *appSetupState = [[AppSetupState alloc] initWithMyIdentityStore:[MyIdentityStore sharedMyIdentityStore]];
  1084. if (migrating || ![appSetupState isAppSetupCompleted]) {
  1085. return;
  1086. }
  1087. EntityManager *entityManager = [[EntityManager alloc] init];
  1088. if (!receivedWhileRunning) {
  1089. NSDictionary *threemaDict = [userInfo objectForKey:@"threema"];
  1090. if (threemaDict != nil) {
  1091. // Decrypt Threema payload if necessary
  1092. threemaDict = [PushPayloadDecryptor decryptPushPayload:threemaDict];
  1093. NSString *cmd = [threemaDict objectForKey:@"cmd"];
  1094. if (cmd != nil) {
  1095. if ([cmd isEqualToString:@"newmsg"]) {
  1096. /* New message push - switch to appropriate conversation */
  1097. NSString *from = [threemaDict objectForKey:@"from"];
  1098. if (from != nil) {
  1099. Contact *contact = [entityManager.entityFetcher contactForId:from];
  1100. if (contact != nil) {
  1101. NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:
  1102. contact, kKeyContact,
  1103. [NSNumber numberWithBool:YES], kKeyForceCompose,
  1104. nil];
  1105. dispatch_async(dispatch_get_main_queue(), ^{
  1106. if (!self.firstPushHandled) {
  1107. [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationShowConversation object:nil userInfo:info];
  1108. self.firstPushHandled = true;
  1109. }
  1110. });
  1111. }
  1112. }
  1113. } else if ([cmd isEqualToString:@"newgroupmsg"]) {
  1114. NSString *from = [threemaDict objectForKey:@"from"];
  1115. if (from != nil) {
  1116. Contact *contact = [entityManager.entityFetcher contactForId:from];
  1117. if (contact != nil) {
  1118. /* Try to find an appropriate group - if there is only one conversation in which
  1119. the sender is a member, then it must be the right one. Otherwise we cannot know */
  1120. NSString *groupId = [threemaDict objectForKey:@"groupId"];
  1121. if (groupId != nil) {
  1122. Conversation *conversation = [entityManager.entityFetcher conversationForGroupId:[[NSData alloc] initWithBase64EncodedString:groupId options:0]];
  1123. if (conversation != nil) {
  1124. NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:
  1125. conversation, kKeyConversation,
  1126. [NSNumber numberWithBool:YES], kKeyForceCompose,
  1127. nil];
  1128. dispatch_async(dispatch_get_main_queue(), ^{
  1129. if (!self.firstPushHandled) {
  1130. [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationShowConversation object:nil userInfo:info];
  1131. self.firstPushHandled = true;
  1132. }
  1133. });
  1134. }
  1135. } else {
  1136. NSArray *groups = [entityManager.entityFetcher conversationsForMember:contact];
  1137. if (groups.count == 1) {
  1138. NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:
  1139. [groups objectAtIndex:0], kKeyConversation,
  1140. [NSNumber numberWithBool:YES], kKeyForceCompose,
  1141. nil];
  1142. dispatch_async(dispatch_get_main_queue(), ^{
  1143. if (!self.firstPushHandled) {
  1144. [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationShowConversation object:nil userInfo:info];
  1145. self.firstPushHandled = true;
  1146. }
  1147. });
  1148. }
  1149. }
  1150. }
  1151. }
  1152. }
  1153. }
  1154. }
  1155. } else {
  1156. if ([[userInfo objectForKey:@"key"] isEqualToString:@"safe-backup-notification"] && notification != nil) {
  1157. [UIAlertTemplate showAlertWithOwner:[self currentTopViewController] title:notification.request.content.title message:notification.request.content.body actionOk:nil];
  1158. }
  1159. }
  1160. }
  1161. #pragma mark - URL & shortcut handling
  1162. - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
  1163. DDLogVerbose(@"openURL: %@", url);
  1164. if (isAppLocked) {
  1165. pendingUrl = url;
  1166. return YES;
  1167. }
  1168. return [URLHandler handleURL:url];
  1169. }
  1170. - (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler {
  1171. BOOL result = NO;
  1172. if (isAppLocked) {
  1173. pendingShortCutItem = shortcutItem;
  1174. } else {
  1175. result = [URLHandler handleShortCutItem:shortcutItem];
  1176. }
  1177. completionHandler(result);
  1178. }
  1179. #pragma mark - UI passcode
  1180. - (BOOL)isPassCodeViewControllerPresented {
  1181. UINavigationController *navVC;
  1182. UIViewController *rootVC = self.window.rootViewController;
  1183. if ([rootVC isKindOfClass:[UINavigationController class]]) {
  1184. navVC = (UINavigationController *) rootVC;
  1185. } else {
  1186. navVC = self.window.rootViewController.navigationController;
  1187. }
  1188. return [navVC.topViewController isKindOfClass:[JKLLockScreenViewController class]];
  1189. }
  1190. - (void)presentPasscodeView {
  1191. if (lockView != nil) {
  1192. [lockView removeFromSuperview];
  1193. }
  1194. BOOL isCallViewControllerPresented = [self.window.rootViewController.presentedViewController isKindOfClass:[CallViewController class]];
  1195. if (isAppLocked && [[KKPasscodeLock sharedLock] isWithinGracePeriod]) {
  1196. [self dismissPasscodeViewAnimated:NO];
  1197. } else if ([[KKPasscodeLock sharedLock] isPasscodeRequired] && [self isPassCodeViewControllerPresented] == NO) {
  1198. JKLLockScreenViewController *vc = [[JKLLockScreenViewController alloc] initWithNibName:NSStringFromClass([JKLLockScreenViewController class]) bundle:[BundleUtil frameworkBundle]];
  1199. vc.lockScreenMode = LockScreenModeNormal;
  1200. vc.delegate = self;
  1201. /* dismiss modal view controller, if any */
  1202. if (!isCallViewControllerPresented) {
  1203. [self.window.rootViewController dismissViewControllerAnimated:NO completion:nil];
  1204. }
  1205. UINavigationController *nav;
  1206. if (SYSTEM_IS_IPAD) {
  1207. nav = [[UINavigationController alloc] initWithNavigationBarClass:[StatusNavigationBar class] toolbarClass:nil];
  1208. } else {
  1209. nav = [[UINavigationController alloc] initWithNavigationBarClass:[StatusNavigationBar class] toolbarClass:nil];
  1210. }
  1211. nav.navigationBarHidden = YES;
  1212. [nav pushViewController:vc animated:NO];
  1213. isAppLocked = YES;
  1214. self.window.rootViewController = nav;
  1215. }
  1216. if (isAppLocked && !isCallViewControllerPresented) {
  1217. if ([[VoIPCallStateManager shared] currentCallState] == CallStateIdle || [VoIPHelper shared].isCallActiveInBackground) {
  1218. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(100 * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{
  1219. [self tryTouchIdAuthentication];
  1220. });
  1221. }
  1222. }
  1223. }
  1224. - (void)dismissPasscodeViewAnimated:(BOOL)animated {
  1225. if (!isAppLocked)
  1226. return;
  1227. [self.window.rootViewController dismissViewControllerAnimated:animated completion:nil];
  1228. [self presentApplicationUI];
  1229. isAppLocked = NO;
  1230. startCheckBiometrics = false;
  1231. isLockscreenDismissed = true;
  1232. if (pendingUrl) {
  1233. [URLHandler handleURL:pendingUrl];
  1234. pendingUrl = nil;
  1235. } else if (pendingShortCutItem) {
  1236. [URLHandler handleShortCutItem:pendingShortCutItem];
  1237. pendingShortCutItem = nil;
  1238. }
  1239. }
  1240. - (void)tryTouchIdAuthentication {
  1241. if ([[KKPasscodeLock sharedLock] isTouchIdOn]) {
  1242. startCheckBiometrics = true;
  1243. }
  1244. [TouchIdAuthentication tryTouchIdAuthenticationCallback:^(BOOL success, NSError *error) {
  1245. dispatch_async(dispatch_get_main_queue(), ^{
  1246. if (success) {
  1247. DDLogVerbose(@"Authenticated using Touch ID.");
  1248. [self dismissPasscodeViewAnimated:YES];
  1249. } else {
  1250. DDLogVerbose(@"Touch ID error: %@", error);
  1251. }
  1252. });
  1253. }];
  1254. }
  1255. #pragma mark - Passcode lock delegate
  1256. - (void)shouldEraseApplicationData:(JKLLockScreenViewController *)viewController {
  1257. DDLogWarn(@"Erase all application data");
  1258. [[MyIdentityStore sharedMyIdentityStore] destroy];
  1259. [[UserReminder sharedUserReminder] markIdentityDeleted];
  1260. /* Remove Core Data stuff */
  1261. [[DatabaseManager dbManager] eraseDB];
  1262. /* Remove files */
  1263. [self removeDirectoryContents:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]];
  1264. [self removeDirectoryContents:[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]];
  1265. [self removeDirectoryContents:NSTemporaryDirectory()];
  1266. /* Reset defaults and turn off passcode */
  1267. [NSUserDefaults resetStandardUserDefaults];
  1268. [AppGroup resetUserDefaults];
  1269. [[KKPasscodeLock sharedLock] disablePasscode];
  1270. [self.window.rootViewController dismissViewControllerAnimated:NO completion:nil];
  1271. NSString *title = [BundleUtil localizedStringForKey:@"all_data_deleted_title"];
  1272. NSString *message = [BundleUtil localizedStringForKey:@"all_data_deleted_message"];
  1273. [UIAlertTemplate showAlertWithOwner:[self currentTopViewController] title:title message:message actionOk:^(UIAlertAction * _Nonnull okAction) {
  1274. exit(0);
  1275. }];
  1276. }
  1277. - (void)didPasscodeEnteredCorrectly:(JKLLockScreenViewController *)viewController {
  1278. isAppLocked = NO;
  1279. startCheckBiometrics = false;
  1280. [self presentApplicationUI];
  1281. if (pendingUrl) {
  1282. [URLHandler handleURL:pendingUrl];
  1283. pendingUrl = nil;
  1284. } else if (pendingShortCutItem) {
  1285. [URLHandler handleShortCutItem:pendingShortCutItem];
  1286. pendingShortCutItem = nil;
  1287. }
  1288. }
  1289. - (void)didPasscodeViewDismiss:(JKLLockScreenViewController *)viewController {
  1290. /* At this point, it's possible that there's no ID but the view controller to generate the key
  1291. has been dismissed because the passcode view was presented. Therefore, we need to present the
  1292. generate controller again */
  1293. isLockscreenDismissed = true;
  1294. AppSetupState *appSetupState = [[AppSetupState alloc] initWithMyIdentityStore:[MyIdentityStore sharedMyIdentityStore]];
  1295. if (![appSetupState isAppSetupCompleted]) {
  1296. [self presentKeyGenerationOrProtectedDataUnavailable];
  1297. }
  1298. }
  1299. - (BOOL)allowTouchIDLockScreenViewController:(JKLLockScreenViewController *)lockScreenViewController {
  1300. startCheckBiometrics = [[KKPasscodeLock sharedLock] isTouchIdOn];
  1301. return [[KKPasscodeLock sharedLock] isTouchIdOn];
  1302. }
  1303. #pragma mark - EnterLicenseDelegate
  1304. - (void)licenseConfirmed {
  1305. [self.window.rootViewController dismissViewControllerAnimated:YES completion:^{
  1306. [[ServerConnector sharedServerConnector] connect];
  1307. }];
  1308. }
  1309. #pragma mark - Accessibility
  1310. - (BOOL)accessibilityPerformMagicTap {
  1311. return [self.magicTapHandler handleMagicTap];
  1312. }
  1313. #pragma mark - PKPushRegistryDelegate
  1314. - (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type {
  1315. if([credentials.token length] == 0) {
  1316. NSLog(@"voip token NULL");
  1317. return;
  1318. }
  1319. if (type == PKPushTypeVoIP) {
  1320. [[ServerConnector sharedServerConnector] setVoIPPushToken:credentials.token];
  1321. }
  1322. }
  1323. - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(nonnull PKPushType)type {
  1324. [self handlePushPayload:payload withCompletionHandler:^{}];
  1325. }
  1326. - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(nonnull PKPushType)type withCompletionHandler:(nonnull void (^)(void))completion {
  1327. [self handlePushPayload:payload withCompletionHandler:completion];
  1328. }
  1329. - (void)handlePushPayload:(PKPushPayload*)payload withCompletionHandler:(nonnull void (^)(void))completion {
  1330. [[ValidationLogger sharedValidationLogger] logString:[NSString stringWithFormat:@"didReceiveIncomingPushWithPayload: %@", payload.dictionaryPayload]];
  1331. [[BackgroundTaskManager shared] newBackgroundTaskWithKey:kAppPushBackgroundTask timeout:kAppPushBackgroundTaskTime completionHandler:^{
  1332. [AppGroup setActive:NO forType:AppGroupTypeShareExtension];
  1333. [[NotificationManager sharedInstance] handleVoIPPush:payload.dictionaryPayload withCompletionHandler:completion];
  1334. }];
  1335. }
  1336. @end