From dd5c4eaec288204c900a96ab1bb675328bd374f9 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 31 Oct 2026 22:53:22 -0500 Subject: [PATCH] Delay the Bluetooth Off alert and make it time sensitive Turning Bluetooth off briefly on purpose (a meeting, a service, at night) set off a critical alarm immediately. Issue it after 10 minutes as a time-sensitive alert instead; turning Bluetooth back on before then retracts the pending alert. Longer outages are still covered by the Loop not running and missing glucose alerts. Closes LoopKit/Loop#2633 --- Loop/Managers/Alerts/AlertManager.swift | 5 +++-- .../Managers/Alerts/AlertManagerTests.swift | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Loop/Managers/Alerts/AlertManager.swift b/Loop/Managers/Alerts/AlertManager.swift index 21c8a5d0ee..54d33126df 100644 --- a/Loop/Managers/Alerts/AlertManager.swift +++ b/Loop/Managers/Alerts/AlertManager.swift @@ -59,6 +59,7 @@ public final class AlertManager { let alertStore: AlertStore private let bluetoothPoweredOffIdentifier = Alert.Identifier(managerIdentifier: managerIdentifier, alertIdentifier: "bluetoothPoweredOff") + static let bluetoothPoweredOffAlertDelay = TimeInterval(minutes: 10) var analyticsServicesManager: AnalyticsServicesManager @@ -177,8 +178,8 @@ public final class AlertManager { await issueAlert(Alert(identifier: bluetoothPoweredOffIdentifier, foregroundContent: fgcontent, backgroundContent: bgcontent, - trigger: .immediate, - interruptionLevel: .critical)) + trigger: .delayed(interval: Self.bluetoothPoweredOffAlertDelay), + interruptionLevel: .timeSensitive)) } } diff --git a/LoopTests/Managers/Alerts/AlertManagerTests.swift b/LoopTests/Managers/Alerts/AlertManagerTests.swift index d6a422f230..44668aba45 100644 --- a/LoopTests/Managers/Alerts/AlertManagerTests.swift +++ b/LoopTests/Managers/Alerts/AlertManagerTests.swift @@ -331,6 +331,24 @@ class AlertManagerTests: XCTestCase { XCTAssertEqual(request.content.sound, .defaultCriticalSound(withAudioVolume: 0)) } } + + func testBluetoothPoweredOffIssuesDelayedTimeSensitiveAlert() async { + mockModalScheduler.alertScheduledExpectation = expectation(description: "alert scheduled") + alertManager.bluetoothDidUpdateState(.poweredOff) + await fulfillment(of: [mockModalScheduler.alertScheduledExpectation!], timeout: 1) + + let alert = mockModalScheduler.scheduledAlert + XCTAssertEqual(alert?.identifier.alertIdentifier, "bluetoothPoweredOff") + XCTAssertEqual(alert?.trigger, .delayed(interval: AlertManager.bluetoothPoweredOffAlertDelay)) + XCTAssertEqual(alert?.interruptionLevel, .timeSensitive) + } + + func testBluetoothPoweredOnRetractsAlert() async { + mockModalScheduler.alertUnscheduledExpectation = expectation(description: "alert unscheduled") + alertManager.bluetoothDidUpdateState(.poweredOn) + await fulfillment(of: [mockModalScheduler.alertUnscheduledExpectation!], timeout: 1) + XCTAssertEqual(mockModalScheduler.unscheduledAlertIdentifier?.alertIdentifier, "bluetoothPoweredOff") + } } extension Swift.Result {