Statistics
| Branch: | Tag: | Revision:

chatsecureios / ChatSecure / Classes / OTRAppDelegate.m @ ab248f1b

History | View | Annotate | Download (22.9 KB)

1
//
2
//  OTRAppDelegate.m
3
//  Off the Record
4
//
5
//  Created by Chris Ballinger on 8/11/11.
6
//  Copyright (c) 2011 Chris Ballinger. All rights reserved.
7
//
8
//  This file is part of ChatSecure.
9
//
10
//  ChatSecure is free software: you can redistribute it and/or modify
11
//  it under the terms of the GNU General Public License as published by
12
//  the Free Software Foundation, either version 3 of the License, or
13
//  (at your option) any later version.
14
//
15
//  ChatSecure is distributed in the hope that it will be useful,
16
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
17
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
//  GNU General Public License for more details.
19
//
20
//  You should have received a copy of the GNU General Public License
21
//  along with ChatSecure.  If not, see <http://www.gnu.org/licenses/>.
22

    
23
#import "OTRAppDelegate.h"
24

    
25
#import "OTRConversationViewController.h"
26

    
27
#import "OTRMessagesHoldTalkViewController.h"
28
#import "OTRSettingsViewController.h"
29
#import "OTRSettingsManager.h"
30

    
31
@import Appirater;
32
#import "OTRConstants.h"
33

    
34
#import "OTRUtilities.h"
35
#import "OTRAccountsManager.h"
36
#import "OTRSettingsManager.h"
37
@import OTRAssets;
38
#import "OTRDatabaseManager.h"
39
@import SAMKeychain;
40

    
41
#import "OTRLog.h"
42
@import CocoaLumberjack;
43
#import "OTRAccount.h"
44
#import "OTRXMPPAccount.h"
45
#import "OTRBuddy.h"
46
@import YapDatabase;
47

    
48
#import "OTRCertificatePinning.h"
49
#import "NSURL+ChatSecure.h"
50
#import "OTRDatabaseUnlockViewController.h"
51
#import "OTRIncomingMessage.h"
52
#import "OTROutgoingMessage.h"
53
#import "OTRPasswordGenerator.h"
54
#import "UIViewController+ChatSecure.h"
55
#import "OTRNotificationController.h"
56
@import XMPPFramework;
57
#import "OTRProtocolManager.h"
58
#import "OTRInviteViewController.h"
59
#import "OTRTheme.h"
60
#import <ChatSecureCore/ChatSecureCore-Swift.h>
61
#import "OTRMessagesViewController.h"
62
#import "OTRXMPPTorAccount.h"
63
@import OTRAssets;
64
@import OTRKit;
65
#import "OTRPushTLVHandlerProtocols.h"
66
#import <KSCrash/KSCrash.h>
67
#import <KSCrash/KSCrashInstallationQuincyHockey.h>
68
#import <KSCrash/KSCrashInstallation+Alert.h>
69
@import UserNotifications;
70

    
71
#import "OTRChatDemo.h"
72

    
73
@interface OTRAppDelegate () <UNUserNotificationCenterDelegate>
74

    
75
@property (nonatomic, strong) OTRSplitViewCoordinator *splitViewCoordinator;
76
@property (nonatomic, strong) OTRSplitViewControllerDelegateObject *splitViewControllerDelegate;
77

    
78
@property (nonatomic, strong) NSTimer *fetchTimer;
79
@property (nonatomic, strong) NSTimer *backgroundTimer;
80
@property (nonatomic) UIBackgroundTaskIdentifier backgroundTask;
81

    
82
@end
83

    
84
@implementation OTRAppDelegate
85
@synthesize window = _window;
86
@dynamic activeThreadYapKey;
87

    
88
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
89
{
90
#if DEBUG
91
    [DDLog addLogger:[DDTTYLogger sharedInstance]];
92
    
93
    DDFileLogger *fileLogger = [[DDFileLogger alloc] init];
94
    fileLogger.rollingFrequency = 0;
95
    fileLogger.maximumFileSize = 0;
96
    [DDLog addLogger:fileLogger withLevel:DDLogLevelAll];
97
#endif
98
    
99
    [self setupCrashReporting];
100
    
101
    _theme = [[[self themeClass] alloc] init];
102
    [self.theme setupGlobalTheme];
103
    
104
    [SAMKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly];
105
    
106
    UIViewController *rootViewController = nil;
107
    
108
    // Create 3 primary view controllers, settings, conversation list and messages
109
    _conversationViewController = [self.theme conversationViewController];
110
    _messagesViewController = [self.theme messagesViewController];
111
    
112
    
113
    if ([OTRDatabaseManager existsYapDatabase] && ![[OTRDatabaseManager sharedInstance] hasPassphrase]) {
114
        // user needs to enter password for current database
115
        rootViewController = [[OTRDatabaseUnlockViewController alloc] init];
116
    } else {
117
        ////// Normal launch to conversationViewController //////
118
        if (![OTRDatabaseManager existsYapDatabase]) {
119
            /**
120
             First Launch
121
             Create password and save to keychain
122
             **/
123
            NSString *newPassword = [OTRPasswordGenerator passwordWithLength:OTRDefaultPasswordLength];
124
            NSError *error = nil;
125
            [[OTRDatabaseManager sharedInstance] setDatabasePassphrase:newPassword remember:YES error:&error];
126
            if (error) {
127
                DDLogError(@"Password Error: %@",error);
128
            }
129
        }
130

    
131
        [[OTRDatabaseManager sharedInstance] setupDatabaseWithName:OTRYapDatabaseName];
132
        rootViewController = [self setupDefaultSplitViewControllerWithLeadingViewController:[[UINavigationController alloc] initWithRootViewController:self.conversationViewController]];
133
        if ([[[NSProcessInfo processInfo] environment][@"OTRLaunchMode"] isEqualToString:@"ChatSecureUITestsDemoData"]) {
134
            [OTRChatDemo loadDemoChatInDatabase];
135
        } else if ([[[NSProcessInfo processInfo] environment][@"OTRLaunchMode"] isEqualToString:@"ChatSecureUITests"]) {
136
            [[OTRDatabaseManager sharedInstance].readWriteDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
137
                [transaction removeAllObjectsInAllCollections];
138
            }];
139
        }
140
    }
141
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
142
    self.window.rootViewController = rootViewController;
143
    
144
    /////////// testing VCs
145
//    OTRXMPPAccount *account = [[OTRXMPPAccount alloc] init];
146
//    account.username = @"test@example.com";
147
//    OTRInviteViewController *vc = [[OTRInviteViewController alloc] initWithAccount:account];
148
//    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
149
//    self.window.rootViewController = nav;
150
    ////////////
151
    
152
    [self.window makeKeyAndVisible];
153
    [TransactionObserver.shared startObserving];
154
    
155
    OTRNotificationController *notificationController = [OTRNotificationController sharedInstance];
156
    [notificationController start];
157
    
158
    if ([PushController getPushPreference] == PushPreferenceEnabled) {
159
        [PushController registerForPushNotifications];
160
    }
161
  
162
    [Appirater setAppId:@"464200063"];
163
    [Appirater setOpenInAppStore:NO];
164
    [Appirater appLaunched:YES];
165
    
166
    [self autoLoginFromBackground:NO];
167
    [application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
168
        
169
    // For disabling screen dimming while plugged in
170
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(batteryStateDidChange:) name:UIDeviceBatteryStateDidChangeNotification object:nil];
171
    [UIDevice currentDevice].batteryMonitoringEnabled = YES;
172
    [self batteryStateDidChange:nil];
173
    
174
    // Setup iOS 10+ in-app notifications
175
    NSOperatingSystemVersion ios10version = {.majorVersion = 10, .minorVersion = 0, .patchVersion = 0};
176
    if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:ios10version]) {
177
        [UNUserNotificationCenter currentNotificationCenter].delegate = self;
178
    }
179

    
180
    
181
    
182
    return YES;
183
}
184

    
185
- (void) setupCrashReporting {
186
    KSCrash *crash = [KSCrash sharedInstance];
187
    crash.monitoring = KSCrashMonitorTypeProductionSafeMinimal;
188
    
189
//#warning Change this to KSCrashMonitorTypeProductionSafeMinimal before App Store release!
190
//#warning Otherwise it may crash for pauses longer than the deadlockWatchdogInterval!
191
    
192
    // People are reporting deadlocks again...
193
    // Let's turn this back on for a little while.
194
#if DEBUG
195
    crash.monitoring = KSCrashMonitorTypeDebuggerSafe;
196
#else
197
    //crash.monitoring = KSCrashMonitorTypeAll;
198
    //crash.deadlockWatchdogInterval = 20;
199
#endif
200
    
201
    // Setup Crash Reporting
202
    KSCrashInstallationHockey* installation = [KSCrashInstallationHockey sharedInstance];
203
    [installation addConditionalAlertWithTitle:Crash_Detected_Title()
204
                                       message:Crash_Detected_Message()
205
                                     yesAnswer:OK_STRING()
206
                                      noAnswer:CANCEL_STRING()];
207

    
208
    installation.appIdentifier = [OTRSecrets hockeyLiveIdentifier];
209
    
210
    [installation install];
211
    [installation sendAllReportsWithCompletion:^(NSArray *filteredReports, BOOL completed, NSError *error)
212
    {
213
        if (error) {
214
            NSLog(@"Error sending KSCrashInstallationHockey reports: %@", error);
215
        } else {
216
            NSLog(@"Sending %d KSCrashInstallationHockey reports.", (int)filteredReports.count);
217
        }
218
    }];
219
}
220

    
221
/**
222
 * This creates a UISplitViewController using a leading view controller (the left view controller). It uses a navigation controller with
223
 * self.messagesViewController as teh right view controller;
224
 * This also creates and sets up teh OTRSplitViewCoordinator
225
 *
226
 * @param leadingViewController The leading or left most view controller in a UISplitViewController. Should most likely be some sort of UINavigationViewController
227
 * @return The base default UISplitViewController
228
 *
229
 */
230
- (UIViewController *)setupDefaultSplitViewControllerWithLeadingViewController:(nonnull UIViewController *)leadingViewController
231
{
232
    
233
    YapDatabaseConnection *connection = [OTRDatabaseManager sharedInstance].readWriteDatabaseConnection;
234
    self.splitViewCoordinator = [[OTRSplitViewCoordinator alloc] initWithDatabaseConnection:connection];
235
    self.splitViewControllerDelegate = [[OTRSplitViewControllerDelegateObject alloc] init];
236
    self.conversationViewController.delegate = self.splitViewCoordinator;
237
    
238
    //MessagesViewController Nav
239
    UINavigationController *messagesNavigationController = [[UINavigationController alloc ]initWithRootViewController:self.messagesViewController];
240
    
241
    //SplitViewController
242
    UISplitViewController *splitViewController = [[UISplitViewController alloc] init];
243
    splitViewController.viewControllers = @[leadingViewController,messagesNavigationController];
244
    splitViewController.delegate = self.splitViewControllerDelegate;
245
    splitViewController.title = CHAT_STRING();
246
    
247
    //setup 'back' button in nav bar
248
    messagesNavigationController.topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem;
249
    messagesNavigationController.topViewController.navigationItem.leftItemsSupplementBackButton = YES;
250
    
251
    self.splitViewCoordinator.splitViewController = splitViewController;
252
    
253
    return splitViewController;
254
}
255

    
256
- (void)showConversationViewController
257
{
258
    self.window.rootViewController = [self setupDefaultSplitViewControllerWithLeadingViewController:[[UINavigationController alloc] initWithRootViewController:self.conversationViewController]];
259
}
260

    
261
- (NSString *)activeThreadYapKey
262
{
263
    NSString *threadOwnerYapKey = nil;
264
    if (self.messagesViewController && [self.messagesViewController otr_isVisible]) {
265
        threadOwnerYapKey = self.messagesViewController.threadKey;
266
    }
267
    return threadOwnerYapKey;
268
}
269

    
270
- (void)applicationWillResignActive:(UIApplication *)application
271
{
272
    [OTRProtocolManager sharedInstance].lastInteractionDate = [NSDate date];
273
    /*
274
     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.
275
     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.
276
     */
277
}
278

    
279
- (void)applicationDidEnterBackground:(UIApplication *)application
280
{
281
    [[OTRProtocolManager sharedInstance] goAwayForAllAccounts];
282
    DDLogInfo(@"Application entered background state.");
283
    NSAssert(self.backgroundTask == UIBackgroundTaskInvalid, nil);
284
    
285
    __block NSUInteger unread = 0;
286
    [[OTRDatabaseManager sharedInstance].readOnlyDatabaseConnection asyncReadWithBlock:^(YapDatabaseReadTransaction *transaction) {
287
        unread = [transaction numberOfUnreadMessages];
288
    } completionBlock:^{
289
        application.applicationIconBadgeNumber = unread;
290
    }];
291
    
292
    self.backgroundTask = [application beginBackgroundTaskWithExpirationHandler: ^{
293
        DDLogInfo(@"Background task expired, disconnecting all accounts. Remaining: %f", application.backgroundTimeRemaining);
294
        if (self.backgroundTimer)
295
        {
296
            [self.backgroundTimer invalidate];
297
            self.backgroundTimer = nil;
298
        }
299
        [[OTRProtocolManager sharedInstance] disconnectAllAccountsSocketOnly:YES timeout:application.backgroundTimeRemaining - .5 completionBlock:^{
300
            [application endBackgroundTask:self.backgroundTask];
301
            self.backgroundTask = UIBackgroundTaskInvalid;
302
        }];
303
    }];
304
    
305
    dispatch_async(dispatch_get_main_queue(), ^{
306
        self.backgroundTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerUpdate:) userInfo:nil repeats:YES];
307
    });
308
}
309
                                
310
- (void) timerUpdate:(NSTimer*)timer {
311
    UIApplication *application = [UIApplication sharedApplication];
312
    NSTimeInterval timeRemaining = application.backgroundTimeRemaining;
313
    DDLogVerbose(@"Timer update, background time left: %f", timeRemaining);
314
}
315

    
316
/** Doesn't stop autoLogin if previous crash when it's a background launch */
317
- (void)autoLoginFromBackground:(BOOL)fromBackground
318
{
319
    [[OTRProtocolManager sharedInstance] loginAccounts:[OTRAccountsManager allAutoLoginAccounts]];
320
}
321

    
322
- (void)applicationWillEnterForeground:(UIApplication *)application
323
{
324
    [Appirater appEnteredForeground:YES];
325
}
326

    
327
- (void)applicationDidBecomeActive:(UIApplication *)application
328
{
329
    [OTRProtocolManager sharedInstance].lastInteractionDate = [NSDate date];
330
    [self autoLoginFromBackground:NO];
331
    /*
332
     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.
333
     */
334
    [self batteryStateDidChange:nil];
335
    
336
    DDLogInfo(@"Application became active");
337
    
338
    if (self.backgroundTimer) 
339
    {
340
        [self.backgroundTimer invalidate];
341
        self.backgroundTimer = nil;
342
    }
343
    if (self.backgroundTask != UIBackgroundTaskInvalid) 
344
    {
345
        [application endBackgroundTask:self.backgroundTask];
346
        self.backgroundTask = UIBackgroundTaskInvalid;
347
    }
348
    
349
    [UIApplication.sharedApplication removeExtraForegroundNotifications];
350
    
351
    if (self.fetchTimer) {
352
        if (self.fetchTimer.isValid) {
353
            NSDictionary *userInfo = self.fetchTimer.userInfo;
354
            void (^completion)(UIBackgroundFetchResult) = [userInfo objectForKey:@"completion"];
355
            // We should probbaly return accurate fetch results
356
            if (completion) {
357
                completion(UIBackgroundFetchResultNewData);
358
            }
359
            [self.fetchTimer invalidate];
360
        }
361
        self.fetchTimer = nil;
362
    }
363
}
364

    
365
- (void)applicationWillTerminate:(UIApplication *)application
366
{
367
    /*
368
     Called when the application is about to terminate.
369
     Save data if appropriate.
370
     See also applicationDidEnterBackground:.
371
     */
372
    
373
    
374
    [[OTRProtocolManager sharedInstance] disconnectAllAccounts];
375
    
376
    //FIXME? [OTRManagedAccount resetAccountsConnectionStatus];
377
    //[OTRUtilities deleteAllBuddiesAndMessages];
378
}
379

    
380
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
381
    [self autoLoginFromBackground:YES];
382
    
383
    self.fetchTimer = [NSTimer scheduledTimerWithTimeInterval:28.5 target:self selector:@selector(fetchTimerUpdate:) userInfo:@{@"completion": completionHandler} repeats:NO];
384
}
385

    
386
- (void) fetchTimerUpdate:(NSTimer*)timer {
387
    void (^completion)(UIBackgroundFetchResult) = timer.userInfo[@"completion"];
388
    NSTimeInterval timeout = [[UIApplication sharedApplication] backgroundTimeRemaining] - .5;
389

    
390
    [[OTRProtocolManager sharedInstance] disconnectAllAccountsSocketOnly:YES timeout:timeout completionBlock:^{
391
        dispatch_async(dispatch_get_main_queue(), ^{
392
            [UIApplication.sharedApplication removeExtraForegroundNotifications];
393
            // We should probably return accurate fetch results
394
            if (completion) {
395
                completion(UIBackgroundFetchResultNewData);
396
            }
397
        });
398
    }];
399
    self.fetchTimer = nil;
400
}
401

    
402
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
403
{
404
    [self application:application performFetchWithCompletionHandler:completionHandler];
405
    
406
    [[OTRProtocolManager sharedInstance].pushController receiveRemoteNotification:userInfo completion:^(OTRBuddy * _Nullable buddy, NSError * _Nullable error) {
407
        // Only show notification if buddy lookup succeeds
408
        if (buddy) {
409
            [application showLocalNotificationForKnockFrom:buddy];
410
        }
411
    }];
412
}
413

    
414
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void(^)(NSArray * __nullable restorableObjects))restorationHandler {
415
    if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
416
        NSURL *url = userActivity.webpageURL;
417
        if ([url otr_isInviteLink]) {
418
            __block XMPPJID *jid = nil;
419
            __block NSString *fingerprint = nil;
420
            NSString *otr = [OTRAccount fingerprintStringTypeForFingerprintType:OTRFingerprintTypeOTR];
421
            [url otr_decodeShareLink:^(XMPPJID * _Nullable inJid, NSArray<NSURLQueryItem*> * _Nullable queryItems) {
422
                jid = inJid;
423
                [queryItems enumerateObjectsUsingBlock:^(NSURLQueryItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
424
                    if ([obj.name isEqualToString:otr]) {
425
                        fingerprint = obj.value;
426
                        *stop = YES;
427
                    }
428
                }];
429
            }];
430
            if (jid) {
431
                [OTRProtocolManager handleInviteForJID:jid otrFingerprint:fingerprint buddyAddedCallback:nil];
432
            }
433
            return YES;
434
        }
435
    }
436
    return NO;
437
}
438

    
439
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
440
    
441
    
442
}
443

    
444
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
445
{
446
    if ([url.scheme isEqualToString:@"xmpp"]) {
447
        XMPPURI *xmppURI = [[XMPPURI alloc] initWithURL:url];
448
        XMPPJID *jid = xmppURI.jid;
449
        NSString *otrFingerprint = xmppURI.queryParameters[@"otr-fingerprint"];
450
        // NSString *action = xmppURI.queryAction; //  && [action isEqualToString:@"subscribe"]
451
        if (jid) {
452
            [OTRProtocolManager handleInviteForJID:jid otrFingerprint:otrFingerprint buddyAddedCallback:^ (OTRBuddy *buddy) {
453
                OTRXMPPBuddy *xmppBuddy = (OTRXMPPBuddy *)buddy;
454
                if (xmppBuddy != nil) {
455
                    NSDictionary *userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:xmppBuddy.threadIdentifier, kOTRNotificationThreadKey, xmppBuddy.threadCollection, kOTRNotificationThreadCollection, nil];
456
                    [self enterThreadWithUserInfo:userInfo];
457
                }
458
            }];
459
            return YES;
460
        }
461
    }
462
    return NO;
463
}
464

    
465

    
466

    
467
- (void) showSubscriptionRequestForBuddy:(NSDictionary*)userInfo {
468
    // This is probably in response to a user requesting subscriptions from us
469
    [self.splitViewCoordinator showConversationsViewController];
470
}
471

    
472
- (void) enterThreadWithUserInfo:(NSDictionary*)userInfo {
473
    NSString *threadKey = userInfo[kOTRNotificationThreadKey];
474
    NSString *threadCollection = userInfo[kOTRNotificationThreadCollection];
475
    NSParameterAssert(threadKey);
476
    NSParameterAssert(threadCollection);
477
    if (!threadKey || !threadCollection) { return; }
478
    __block id <OTRThreadOwner> thread = nil;
479
    [[OTRDatabaseManager sharedInstance].readOnlyDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
480
        thread = [transaction objectForKey:threadKey inCollection:threadCollection];
481
    }];
482
    if (thread) {
483
        [self.splitViewCoordinator enterConversationWithThread:thread sender:self];
484
    }
485
}
486

    
487
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
488
{
489
    [[NSNotificationCenter defaultCenter] postNotificationName:OTRUserNotificationsChanged object:self userInfo:@{@"settings": notificationSettings}];
490
    if (notificationSettings.types == UIUserNotificationTypeNone) {
491
        NSLog(@"Push notifications disabled by user.");
492
    } else {
493
        [application registerForRemoteNotifications];
494
    }
495
}
496

    
497
- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(nonnull NSData *)deviceToken
498
{
499
    [[OTRProtocolManager sharedInstance].pushController didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
500
}
501

    
502
- (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err {
503
    [[NSNotificationCenter defaultCenter] postNotificationName:OTRFailedRemoteNotificationRegistration object:self userInfo:@{kOTRNotificationErrorKey:err}];
504
    DDLogError(@"Error in registration. Error: %@%@", [err localizedDescription], [err userInfo]);
505
}
506

    
507
// To improve usability, keep the app open when you're plugged in
508
- (void) batteryStateDidChange:(NSNotification*)notification {
509
    UIDeviceBatteryState currentState = [[UIDevice currentDevice] batteryState];
510
    if (currentState == UIDeviceBatteryStateCharging || currentState == UIDeviceBatteryStateFull) {
511
        [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
512
    } else {
513
        [[UIApplication sharedApplication] setIdleTimerDisabled:NO];
514
    }
515
}
516

    
517
#pragma mark UNUserNotificationCenterDelegate methods (iOS 10+)
518

    
519
- (void) userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
520
    if ([notification.request.content.threadIdentifier isEqualToString:[self activeThreadYapKey]]) {
521
        completionHandler(UNNotificationPresentationOptionNone);
522
    } else {
523
        completionHandler(UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert);
524
    }
525
}
526

    
527
- (void) userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler {
528
    NSDictionary *userInfo = response.notification.request.content.userInfo;
529
    if ([userInfo[kOTRNotificationType] isEqualToString:kOTRNotificationTypeNone]) {
530
        // Nothing
531
    } else if ([userInfo[kOTRNotificationType] isEqualToString:kOTRNotificationTypeSubscriptionRequest]) {
532
        // This is a subscription request
533
        [self showSubscriptionRequestForBuddy:userInfo];
534
    } else {
535
        [self enterThreadWithUserInfo:userInfo];
536
    }
537
    completionHandler();
538
}
539

    
540
#pragma - mark Class Methods
541
+ (instancetype)appDelegate
542
{
543
    return (OTRAppDelegate*)[[UIApplication sharedApplication] delegate];
544
}
545

    
546
#pragma mark - Theming
547

    
548
- (Class) themeClass {
549
    return [OTRTheme class];
550
}
551

    
552
@end