diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a7b135f9..8dc2ad0ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ Changelog ========= +## TBD + +### Bug fixes + +* Show correct value for `app.inForeground` when an app launches and crashes in + the background without ever coming to the foreground. + [#415](https://github.com/bugsnag/bugsnag-cocoa/pull/415) + ## 5.22.6 (2019-09-18) ### Enhancements diff --git a/Source/BSGOutOfMemoryWatchdog.m b/Source/BSGOutOfMemoryWatchdog.m index b2f7e6b4d..5850a7808 100644 --- a/Source/BSGOutOfMemoryWatchdog.m +++ b/Source/BSGOutOfMemoryWatchdog.m @@ -244,19 +244,8 @@ - (NSMutableDictionary *)generateCacheInfoWithConfig:(BugsnagConfiguration *)con app[@"version"] = systemInfo[@BSG_KSSystemField_BundleShortVersion] ?: @""; app[@"bundleVersion"] = systemInfo[@BSG_KSSystemField_BundleVersion] ?: @""; #if BSGOOMAvailable - UIApplicationState state = [self currentAppState]; - // The app is in the foreground if the current state is "active" or - // "inactive". From the UIApplicationState docs: - // > UIApplicationStateActive - // > The app is running in the foreground and currently receiving events. - // > UIApplicationStateInactive - // > The app is running in the foreground but is not receiving events. - // > This might happen as a result of an interruption or because the app - // > is transitioning to or from the background. - // > UIApplicationStateBackground - // > The app is running in the background. - app[@"inForeground"] = @(state == UIApplicationStateInactive - || state == UIApplicationStateActive); + UIApplicationState state = [BSG_KSSystemInfo currentAppState]; + app[@"inForeground"] = @([BSG_KSSystemInfo isInForeground:state]); app[@"isActive"] = @(state == UIApplicationStateActive); #else app[@"inForeground"] = @YES; @@ -285,34 +274,4 @@ - (NSMutableDictionary *)generateCacheInfoWithConfig:(BugsnagConfiguration *)con return cache; } -// Only available on iOS/tvOS -#if BSGOOMAvailable -- (UIApplicationState)currentAppState { - // Only checked outside of app extensions since sharedApplication is - // unavailable to extension UIKit APIs - if ([BSG_KSSystemInfo isRunningInAppExtension]) { - return UIApplicationStateActive; - } - - UIApplicationState(^getState)(void) = ^() { - // Calling this API indirectly to avoid a compile-time check that - // [UIApplication sharedApplication] is not called from app extensions - // (which is handled above) - UIApplication *app = [UIApplication performSelector:@selector(sharedApplication)]; - return [app applicationState]; - }; - - if ([[NSThread currentThread] isMainThread]) { - return getState(); - } else { - // [UIApplication sharedApplication] is a main thread-only API - __block UIApplicationState state; - dispatch_sync(dispatch_get_main_queue(), ^{ - state = getState(); - }); - return state; - } -} -#endif - @end diff --git a/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashState.m b/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashState.m index 0d128e7ff..7c47457d7 100644 --- a/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashState.m +++ b/Source/KSCrash/Source/KSCrash/Recording/BSG_KSCrashState.m @@ -30,10 +30,14 @@ #include "BSG_KSJSONCodec.h" #include "BSG_KSJSONCodecObjC.h" #include "BSG_KSMach.h" +#include "BSG_KSSystemInfo.h" //#define BSG_KSLogger_LocalLevel TRACE #include "BSG_KSLogger.h" +#if (TARGET_OS_TV || TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR) +#import +#endif #include #include #include @@ -298,7 +302,14 @@ bool bsg_kscrashstate_init(const char *const stateFilePath, // Simulate first transition to foreground state->launchesSinceLastCrash++; state->sessionsSinceLastCrash++; +#if (TARGET_OS_TV || TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR) + // On iOS/tvOS, the app may have launched in the background due to a fetch + // event or notification + UIApplicationState appState = [BSG_KSSystemInfo currentAppState]; + state->applicationIsInForeground = [BSG_KSSystemInfo isInForeground:appState]; +#else state->applicationIsInForeground = true; +#endif return bsg_kscrashstate_i_saveState(state, stateFilePath); } diff --git a/Source/KSCrash/Source/KSCrash/Recording/BSG_KSSystemInfo.h b/Source/KSCrash/Source/KSCrash/Recording/BSG_KSSystemInfo.h index a4c9eb7a7..421b74120 100644 --- a/Source/KSCrash/Source/KSCrash/Recording/BSG_KSSystemInfo.h +++ b/Source/KSCrash/Source/KSCrash/Recording/BSG_KSSystemInfo.h @@ -56,6 +56,9 @@ #define BSG_KSSystemField_BuildType "build_type" #import +#if TARGET_OS_TV || TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE +#import +#endif /** * Provides system information useful for a crash report. @@ -78,4 +81,13 @@ */ + (BOOL)isRunningInAppExtension; +#if TARGET_OS_TV || TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE ++ (UIApplicationState)currentAppState; + +/** + * YES if the app is currently shown in the foreground + */ ++ (BOOL)isInForeground:(UIApplicationState)state; +#endif + @end diff --git a/Source/KSCrash/Source/KSCrash/Recording/BSG_KSSystemInfo.m b/Source/KSCrash/Source/KSCrash/Recording/BSG_KSSystemInfo.m index 140b742b6..0889980d7 100644 --- a/Source/KSCrash/Source/KSCrash/Recording/BSG_KSSystemInfo.m +++ b/Source/KSCrash/Source/KSCrash/Recording/BSG_KSSystemInfo.m @@ -453,6 +453,50 @@ + (BOOL)isRunningInAppExtension { #endif } +#if TARGET_OS_TV || TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE ++ (UIApplicationState)currentAppState { + // Only checked outside of app extensions since sharedApplication is + // unavailable to extension UIKit APIs + if ([self isRunningInAppExtension]) { + return UIApplicationStateActive; + } + + UIApplicationState(^getState)(void) = ^() { + // Calling this API indirectly to avoid a compile-time check that + // [UIApplication sharedApplication] is not called from app extensions + // (which is handled above) + UIApplication *app = [UIApplication performSelector:@selector(sharedApplication)]; + return [app applicationState]; + }; + + if ([[NSThread currentThread] isMainThread]) { + return getState(); + } else { + // [UIApplication sharedApplication] is a main thread-only API + __block UIApplicationState state; + dispatch_sync(dispatch_get_main_queue(), ^{ + state = getState(); + }); + return state; + } +} + ++ (BOOL)isInForeground:(UIApplicationState)state { + // The app is in the foreground if the current state is "active" or + // "inactive". From the UIApplicationState docs: + // > UIApplicationStateActive + // > The app is running in the foreground and currently receiving events. + // > UIApplicationStateInactive + // > The app is running in the foreground but is not receiving events. + // > This might happen as a result of an interruption or because the app + // > is transitioning to or from the background. + // > UIApplicationStateBackground + // > The app is running in the background. + return state == UIApplicationStateInactive + || state == UIApplicationStateActive; +} +#endif + @end const char *bsg_kssysteminfo_toJSON(void) { diff --git a/Tests/KSCrash/KSSystemInfo_Tests.m b/Tests/KSCrash/KSSystemInfo_Tests.m index a8c7e9926..a3e3ca531 100755 --- a/Tests/KSCrash/KSSystemInfo_Tests.m +++ b/Tests/KSCrash/KSSystemInfo_Tests.m @@ -75,4 +75,24 @@ - (void) testExecutablePathIsValid XCTAssertEqualObjects(executablePath, expectedExecutablePath); } +#if TARGET_OS_TV || TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE +- (void)testCurrentAppState { + // Should default to active as tests aren't in an app bundle + XCTAssertEqual(UIApplicationStateActive, [BSG_KSSystemInfo currentAppState]); +} + +- (void)testInactiveIsInForeground { + XCTAssertTrue([BSG_KSSystemInfo isInForeground:UIApplicationStateInactive]); +} + +- (void)testActiveIsInForeground { + XCTAssertTrue([BSG_KSSystemInfo isInForeground:UIApplicationStateActive]); + +} + +- (void)testBackgroundIsNotInForeground { + XCTAssertFalse([BSG_KSSystemInfo isInForeground:UIApplicationStateBackground]); +} +#endif + @end