diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 9ddd842c8..6d2398a9b 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -233,7 +233,6 @@ typedef enum : NSUInteger { @property (nonatomic) BOOL isViewCompletelyAppeared; @property (nonatomic) BOOL isViewVisible; -@property (nonatomic) BOOL isAppInBackground; @property (nonatomic) BOOL shouldObserveDBModifications; @property (nonatomic) BOOL viewHasEverAppeared; @property (nonatomic) BOOL hasUnreadMessages; @@ -594,12 +593,12 @@ typedef enum : NSUInteger { - (void)applicationWillEnterForeground:(NSNotification *)notification { [self startReadTimer]; - self.isAppInBackground = NO; + [self updateCellsVisible]; } - (void)applicationDidEnterBackground:(NSNotification *)notification { - self.isAppInBackground = YES; + [self updateCellsVisible]; if (self.hasClearedUnreadMessagesIndicator) { self.hasClearedUnreadMessagesIndicator = NO; [self.dynamicInteractions clearUnreadIndicatorState]; @@ -609,6 +608,7 @@ typedef enum : NSUInteger { - (void)applicationWillResignActive:(NSNotification *)notification { + [self updateShouldObserveDBModifications]; [self cancelVoiceMemo]; self.isUserScrolling = NO; [self saveDraft]; @@ -620,6 +620,7 @@ typedef enum : NSUInteger { - (void)applicationDidBecomeActive:(NSNotification *)notification { + [self updateShouldObserveDBModifications]; [self startReadTimer]; } @@ -1530,7 +1531,8 @@ typedef enum : NSUInteger { - (void)autoLoadMoreIfNecessary { - if (self.isUserScrolling || !self.isViewVisible || self.isAppInBackground) { + BOOL isMainAppAndActive = CurrentAppContext().isMainAppAndActive; + if (self.isUserScrolling || !self.isViewVisible || !isMainAppAndActive) { return; } if (!self.showLoadMoreHeader) { @@ -4428,17 +4430,10 @@ typedef enum : NSUInteger { [self updateCellsVisible]; } -- (void)setIsAppInBackground:(BOOL)isAppInBackground -{ - _isAppInBackground = isAppInBackground; - - [self updateShouldObserveDBModifications]; - [self updateCellsVisible]; -} - - (void)updateCellsVisible { - BOOL isCellVisible = self.isViewVisible && !self.isAppInBackground; + BOOL isAppInBackground = CurrentAppContext().isInBackground; + BOOL isCellVisible = self.isViewVisible && !isAppInBackground; for (ConversationViewCell *cell in self.collectionView.visibleCells) { cell.isCellVisible = isCellVisible; } @@ -4446,7 +4441,8 @@ typedef enum : NSUInteger { - (void)updateShouldObserveDBModifications { - self.shouldObserveDBModifications = self.isViewVisible && !self.isAppInBackground; + BOOL isAppForegroundAndActive = CurrentAppContext().isAppForegroundAndActive; + self.shouldObserveDBModifications = self.isViewVisible && isAppForegroundAndActive; } - (void)setShouldObserveDBModifications:(BOOL)shouldObserveDBModifications diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index e68963b39..d47030869 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -51,7 +51,6 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations @property (nonatomic) NSSet *blockedPhoneNumberSet; @property (nonatomic, readonly) NSCache *threadViewModelCache; @property (nonatomic) BOOL isViewVisible; -@property (nonatomic) BOOL isAppInBackground; @property (nonatomic) BOOL shouldObserveDBModifications; @property (nonatomic) BOOL hasBeenPresented; @@ -131,14 +130,14 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations selector:@selector(applicationWillEnterForeground:) name:OWSApplicationWillEnterForegroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationDidEnterBackground:) - name:OWSApplicationDidEnterBackgroundNotification - object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:OWSApplicationDidBecomeActiveNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(applicationWillResignActive:) + name:OWSApplicationWillResignActiveNotification + object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(yapDatabaseModified:) name:YapDatabaseModifiedNotification @@ -484,16 +483,10 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations [self updateShouldObserveDBModifications]; } -- (void)setIsAppInBackground:(BOOL)isAppInBackground -{ - _isAppInBackground = isAppInBackground; - - [self updateShouldObserveDBModifications]; -} - - (void)updateShouldObserveDBModifications { - self.shouldObserveDBModifications = self.isViewVisible && !self.isAppInBackground; + BOOL isAppForegroundAndActive = CurrentAppContext().isAppForegroundAndActive; + self.shouldObserveDBModifications = self.isViewVisible && isAppForegroundAndActive; } - (void)setShouldObserveDBModifications:(BOOL)shouldObserveDBModifications @@ -550,15 +543,9 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations - (void)applicationWillEnterForeground:(NSNotification *)notification { - self.isAppInBackground = NO; [self checkIfEmptyView]; } -- (void)applicationDidEnterBackground:(NSNotification *)notification -{ - self.isAppInBackground = YES; -} - - (BOOL)hasAnyMessagesWithTransaction:(YapDatabaseReadTransaction *)transaction { return [TSThread numberOfKeysInCollectionWithTransaction:transaction] > 0; @@ -566,6 +553,8 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations - (void)applicationDidBecomeActive:(NSNotification *)notification { + [self updateShouldObserveDBModifications]; + // It's possible a thread was created while we where in the background. But since we don't honor contact // requests unless the app is in the foregrond, we must check again here upon becoming active. __block BOOL hasAnyMessages; @@ -582,6 +571,11 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations } } +- (void)applicationWillResignActive:(NSNotification *)notification +{ + [self updateShouldObserveDBModifications]; +} + #pragma mark - startup - (NSArray *)unseenUpgradeExperiences diff --git a/Signal/src/util/MainAppContext.m b/Signal/src/util/MainAppContext.m index 5d7f708e1..7fed2dc6b 100644 --- a/Signal/src/util/MainAppContext.m +++ b/Signal/src/util/MainAppContext.m @@ -11,6 +11,14 @@ NS_ASSUME_NONNULL_BEGIN +@interface MainAppContext () + +@property (atomic) UIApplicationState reportedApplicationState; + +@end + +#pragma mark - + @implementation MainAppContext @synthesize mainWindow = _mainWindow; @@ -23,6 +31,8 @@ NS_ASSUME_NONNULL_BEGIN return self; } + self.reportedApplicationState = UIApplicationStateInactive; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification @@ -58,6 +68,8 @@ NS_ASSUME_NONNULL_BEGIN { OWSAssertIsOnMainThread(); + self.reportedApplicationState = UIApplicationStateInactive; + DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); [NSNotificationCenter.defaultCenter postNotificationName:OWSApplicationWillEnterForegroundNotification object:nil]; @@ -67,6 +79,8 @@ NS_ASSUME_NONNULL_BEGIN { OWSAssertIsOnMainThread(); + self.reportedApplicationState = UIApplicationStateBackground; + DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); [DDLog flushLog]; @@ -77,6 +91,8 @@ NS_ASSUME_NONNULL_BEGIN { OWSAssertIsOnMainThread(); + self.reportedApplicationState = UIApplicationStateInactive; + DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); [DDLog flushLog]; @@ -87,6 +103,8 @@ NS_ASSUME_NONNULL_BEGIN { OWSAssertIsOnMainThread(); + self.reportedApplicationState = UIApplicationStateActive; + DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); [NSNotificationCenter.defaultCenter postNotificationName:OWSApplicationDidBecomeActiveNotification object:nil]; @@ -135,12 +153,12 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)isInBackground { - return [UIApplication sharedApplication].applicationState == UIApplicationStateBackground; + return self.reportedApplicationState == UIApplicationStateBackground; } -- (UIApplicationState)mainApplicationState +- (BOOL)isAppForegroundAndActive { - return [UIApplication sharedApplication].applicationState; + return self.reportedApplicationState == UIApplicationStateActive; } - (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler: diff --git a/SignalMessaging/Views/ThreadViewHelper.m b/SignalMessaging/Views/ThreadViewHelper.m index 3050122ce..e05169a39 100644 --- a/SignalMessaging/Views/ThreadViewHelper.m +++ b/SignalMessaging/Views/ThreadViewHelper.m @@ -56,25 +56,30 @@ NS_ASSUME_NONNULL_BEGIN [self.uiDatabaseConnection beginLongLivedReadTransaction]; [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationWillEnterForeground:) - name:OWSApplicationWillEnterForegroundNotification + selector:@selector(applicationDidBecomeActive:) + name:OWSApplicationDidBecomeActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationDidEnterBackground:) - name:OWSApplicationDidEnterBackgroundNotification + selector:@selector(applicationWillResignActive:) + name:OWSApplicationWillResignActiveNotification object:nil]; - self.shouldObserveDBModifications = !CurrentAppContext().isInBackground; + [self updateShouldObserveDBModifications]; } -- (void)applicationWillEnterForeground:(NSNotification *)notification +- (void)applicationDidBecomeActive:(NSNotification *)notification { - self.shouldObserveDBModifications = YES; + [self updateShouldObserveDBModifications]; } -- (void)applicationDidEnterBackground:(NSNotification *)notification +- (void)applicationWillResignActive:(NSNotification *)notification { - self.shouldObserveDBModifications = NO; + [self updateShouldObserveDBModifications]; +} + +- (void)updateShouldObserveDBModifications +{ + self.shouldObserveDBModifications = CurrentAppContext().isAppForegroundAndActive; } // Don't observe database change notifications when the app is in the background. diff --git a/SignalServiceKit/src/Storage/OWSStorage.m b/SignalServiceKit/src/Storage/OWSStorage.m index d4dd65b1b..2145d78b9 100644 --- a/SignalServiceKit/src/Storage/OWSStorage.m +++ b/SignalServiceKit/src/Storage/OWSStorage.m @@ -720,9 +720,9 @@ NSString *const kNSUserDefaults_DatabaseExtensionVersionMap = @"kNSUserDefaults_ stringWithFormat:@"CipherKeySpec inaccessible. New install or no unlock since device restart? Error: %@", error]; if (CurrentAppContext().isMainApp) { - UIApplicationState applicationState = CurrentAppContext().mainApplicationState; - errorDescription = - [errorDescription stringByAppendingFormat:@", ApplicationState: %d", (int)applicationState]; + UIApplicationState applicationState = CurrentAppContext().reportedApplicationState; + errorDescription = [errorDescription + stringByAppendingFormat:@", ApplicationState: %@", NSStringForUIApplicationState(applicationState)]; } DDLogError(@"%@ %@", self.logTag, errorDescription); [DDLog flushLog]; diff --git a/SignalServiceKit/src/Util/AppContext.h b/SignalServiceKit/src/Util/AppContext.h index 1feea801e..c6c665d95 100755 --- a/SignalServiceKit/src/Util/AppContext.h +++ b/SignalServiceKit/src/Util/AppContext.h @@ -33,15 +33,33 @@ NSString *NSStringForUIApplicationState(UIApplicationState value); @property (atomic, nullable) UIWindow *mainWindow; -// Should only be called if isMainApp is YES. +// Unlike UIApplication.applicationState, this is thread-safe. +// It contains the "last known" application state. +// +// Because it is updated in response to "will/did-style" events, it is +// conservative and skews toward less-active and not-foreground: +// +// * It doesn't report "is active" until the app is active +// and reports "inactive" as soon as it _will become_ inactive. +// * It doesn't report "is foreground (but inactive)" until the app is +// foreground & inactive and reports "background" as soon as it _will +// enter_ background. // -// Wherever possible, use isMainAppAndActive or isInBackground instead. -// This should only be used by debugging/logging code. -- (UIApplicationState)mainApplicationState; +// This conservatism is useful, since we want to err on the side of +// caution when, for example, we do work that should only be done +// when the app is foreground and active. +@property (atomic, readonly) UIApplicationState reportedApplicationState; -// Similar to UIApplicationStateBackground, but works in SAE. +// A convenience accessor for reportedApplicationState. +// +// This method is thread-safe. - (BOOL)isInBackground; +// A convenience accessor for reportedApplicationState. +// +// This method is thread-safe. +- (BOOL)isAppForegroundAndActive; + // Should start a background task if isMainApp is YES. // Should just return UIBackgroundTaskInvalid if isMainApp is NO. - (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler: diff --git a/SignalShareExtension/utils/ShareAppExtensionContext.m b/SignalShareExtension/utils/ShareAppExtensionContext.m index 6bf57d5b0..b7baa134d 100644 --- a/SignalShareExtension/utils/ShareAppExtensionContext.m +++ b/SignalShareExtension/utils/ShareAppExtensionContext.m @@ -11,7 +11,8 @@ NS_ASSUME_NONNULL_BEGIN @interface ShareAppExtensionContext () @property (nonatomic) UIViewController *rootViewController; -@property (atomic) BOOL isSAEInBackground; + +@property (atomic) UIApplicationState reportedApplicationState; @end @@ -33,6 +34,8 @@ NS_ASSUME_NONNULL_BEGIN _rootViewController = rootViewController; + self.reportedApplicationState = UIApplicationStateInactive; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(extensionHostDidBecomeActive:) name:NSExtensionHostDidBecomeActiveNotification @@ -66,7 +69,7 @@ NS_ASSUME_NONNULL_BEGIN DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); - self.isSAEInBackground = NO; + self.reportedApplicationState = UIApplicationStateActive; [NSNotificationCenter.defaultCenter postNotificationName:OWSApplicationDidBecomeActiveNotification object:nil]; } @@ -75,6 +78,8 @@ NS_ASSUME_NONNULL_BEGIN { OWSAssertIsOnMainThread(); + self.reportedApplicationState = UIApplicationStateInactive; + DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); [DDLog flushLog]; @@ -88,7 +93,7 @@ NS_ASSUME_NONNULL_BEGIN DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); [DDLog flushLog]; - self.isSAEInBackground = YES; + self.reportedApplicationState = UIApplicationStateBackground; [NSNotificationCenter.defaultCenter postNotificationName:OWSApplicationDidEnterBackgroundNotification object:nil]; } @@ -99,7 +104,7 @@ NS_ASSUME_NONNULL_BEGIN DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); - self.isSAEInBackground = NO; + self.reportedApplicationState = UIApplicationStateInactive; [NSNotificationCenter.defaultCenter postNotificationName:OWSApplicationWillEnterForegroundNotification object:nil]; } @@ -143,13 +148,12 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)isInBackground { - return self.isSAEInBackground; + return self.reportedApplicationState == UIApplicationStateBackground; } -- (UIApplicationState)mainApplicationState +- (BOOL)isAppForegroundAndActive { - OWSFail(@"%@ called %s.", self.logTag, __PRETTY_FUNCTION__); - return UIApplicationStateBackground; + return self.reportedApplicationState == UIApplicationStateActive; } - (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler: