diff --git a/CHANGELOG.md b/CHANGELOG.md index 29fd1b1489..7d13d41a8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Features + +- Track low power mode in device context (#7777) + ## 9.10.0 ### Features diff --git a/SentryTestUtils/Sources/TestSentryNSProcessInfoWrapper.swift b/SentryTestUtils/Sources/TestSentryNSProcessInfoWrapper.swift index c4e411cf49..4e5addea3a 100644 --- a/SentryTestUtils/Sources/TestSentryNSProcessInfoWrapper.swift +++ b/SentryTestUtils/Sources/TestSentryNSProcessInfoWrapper.swift @@ -13,6 +13,7 @@ public var isiOSAppOnMac: Bool? public var isMacCatalystApp: Bool? public var isiOSAppOnVisionOS: Bool? + public var isLowPowerModeEnabled: Bool? } public var overrides = Override() @@ -50,4 +51,9 @@ public var isiOSAppOnVisionOS: Bool { return overrides.isiOSAppOnVisionOS ?? ProcessInfo.processInfo.isiOSAppOnVisionOS } + + @available(macOS 12.0, *) + public var isLowPowerModeEnabled: Bool { + return overrides.isLowPowerModeEnabled ?? ProcessInfo.processInfo.isLowPowerModeEnabled + } } diff --git a/Sources/Swift/Helper/SentryExtraContextProvider.swift b/Sources/Swift/Helper/SentryExtraContextProvider.swift index b1cbd28f17..e781fe7e46 100644 --- a/Sources/Swift/Helper/SentryExtraContextProvider.swift +++ b/Sources/Swift/Helper/SentryExtraContextProvider.swift @@ -52,6 +52,10 @@ default: SentrySDKLog.warning("Unexpected thermal state enum value: \(thermalState)") } + + if #available(macOS 12.0, *) { + extraDeviceContext["low_power_mode"] = NSNumber(value: processInfoWrapper.isLowPowerModeEnabled) + } #if (os(iOS)) && !SENTRY_NO_UI_FRAMEWORK if deviceWrapper.orientation != .unknown { diff --git a/Sources/Swift/Helper/SentryProcessInfo.swift b/Sources/Swift/Helper/SentryProcessInfo.swift index ee2eeb821c..1765e339be 100644 --- a/Sources/Swift/Helper/SentryProcessInfo.swift +++ b/Sources/Swift/Helper/SentryProcessInfo.swift @@ -11,8 +11,11 @@ @available(macOS 12.0, *) var isMacCatalystApp: Bool { get } - + var isiOSAppOnVisionOS: Bool { get } + + @available(macOS 12.0, *) + var isLowPowerModeEnabled: Bool { get } } // This is needed because a file that only contains an @objc extension will get automatically stripped out diff --git a/Sources/Swift/Integrations/SentryCrash/SentryCrashIntegration.swift b/Sources/Swift/Integrations/SentryCrash/SentryCrashIntegration.swift index f5d8f3acc5..cdfae38cb5 100644 --- a/Sources/Swift/Integrations/SentryCrash/SentryCrashIntegration.swift +++ b/Sources/Swift/Integrations/SentryCrash/SentryCrashIntegration.swift @@ -113,6 +113,14 @@ final class SentryCrashIntegration: NSOb name: NSLocale.currentLocaleDidChangeNotification, object: nil ) + + if #available(macOS 12.0, *) { + NotificationCenter.default.removeObserver( + self, + name: NSNotification.Name.NSProcessInfoPowerStateDidChange, + object: nil + ) + } } // MARK: - Crash Handler @@ -241,6 +249,16 @@ final class SentryCrashIntegration: NSOb name: NSLocale.currentLocaleDidChangeNotification, object: nil ) + + if #available(macOS 12.0, *) { + updateLowPowerModeContext() + NotificationCenter.default.addObserver( + self, + selector: #selector(powerStateDidChange), + name: NSNotification.Name.NSProcessInfoPowerStateDidChange, + object: nil + ) + } } // Exposed to objc for the NotificationCenter in configureScope() @@ -261,6 +279,29 @@ final class SentryCrashIntegration: NSOb } } + @available(macOS 12.0, *) + @objc private func powerStateDidChange() { + updateLowPowerModeContext() + } + + @available(macOS 12.0, *) + private func updateLowPowerModeContext() { + let isLowPowerMode = ProcessInfo.processInfo.isLowPowerModeEnabled + SentrySDKInternal.currentHub().configureScope { scope in + var device: [String: Any] + let contextDictionary = scope.contextDictionary + if let existingDevice = contextDictionary[SENTRY_CONTEXT_DEVICE_KEY] as? [String: Any] { + device = existingDevice + } else { + device = [:] + } + + device["low_power_mode"] = isLowPowerMode + + scope.setContext(value: device, key: SENTRY_CONTEXT_DEVICE_KEY) + } + } + // MARK: - Tracing Configuration private func configureTracingWhenCrashing() { diff --git a/Tests/SentryTests/Helper/SentryExtraContextProviderTests.swift b/Tests/SentryTests/Helper/SentryExtraContextProviderTests.swift index f1b96319ad..7de9914a7b 100644 --- a/Tests/SentryTests/Helper/SentryExtraContextProviderTests.swift +++ b/Tests/SentryTests/Helper/SentryExtraContextProviderTests.swift @@ -79,4 +79,24 @@ final class SentryExtraContextProviderTests: XCTestCase { XCTAssertEqual(try XCTUnwrap(device["thermal_state"] as? String), "critical") } + func testLowPowerMode() throws { + let sut = fixture.getSut() + fixture.processWrapper.overrides.isLowPowerModeEnabled = true + + let actualContext = sut.getExtraContext() + let device = try XCTUnwrap(actualContext["device"] as? [String: Any]) + + XCTAssertTrue(try XCTUnwrap(device["low_power_mode"] as? Bool)) + } + + func testLowPowerModeDisabled() throws { + let sut = fixture.getSut() + fixture.processWrapper.overrides.isLowPowerModeEnabled = false + + let actualContext = sut.getExtraContext() + let device = try XCTUnwrap(actualContext["device"] as? [String: Any]) + + XCTAssertFalse(try XCTUnwrap(device["low_power_mode"] as? Bool)) + } + }