mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 21:05:20 +00:00
Fix iOS background Bluetooth connectivity issues (#378)
- Implement Core Bluetooth state restoration with restoration identifiers - Add background task management to protect critical BLE operations - Fix aggressive battery optimization that killed background connectivity - Disable scan duty cycling in background (was causing 89-97% downtime) - Add missing didEnterBackground/willEnterForeground notifications - Fix advertising cutoff in low battery conditions (now only <10%) - Add background-processing entitlement to Info.plist - Optimize scan parameters for background vs foreground modes These changes address the issue where devices lose mesh connectivity when the app is backgrounded, ensuring continuous Bluetooth operation within iOS background execution limits. Co-authored-by: jack <jackjackbits@users.noreply.github.com>
This commit is contained in:
@@ -40,6 +40,7 @@
|
|||||||
<string>bluetooth-central</string>
|
<string>bluetooth-central</string>
|
||||||
<string>bluetooth-peripheral</string>
|
<string>bluetooth-peripheral</string>
|
||||||
<string>remote-notification</string>
|
<string>remote-notification</string>
|
||||||
|
<string>background-processing</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
|
|||||||
@@ -601,6 +601,17 @@ class BluetoothMeshService: NSObject {
|
|||||||
// App state tracking
|
// App state tracking
|
||||||
private var isAppInForeground = true
|
private var isAppInForeground = true
|
||||||
|
|
||||||
|
// Background task management
|
||||||
|
#if os(iOS)
|
||||||
|
private var backgroundTask: UIBackgroundTaskIdentifier = .invalid
|
||||||
|
private var scanBackgroundTask: UIBackgroundTaskIdentifier = .invalid
|
||||||
|
private var advertiseBackgroundTask: UIBackgroundTaskIdentifier = .invalid
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Core Bluetooth state restoration identifiers
|
||||||
|
private static let centralManagerRestorationID = "com.bitchat.central"
|
||||||
|
private static let peripheralManagerRestorationID = "com.bitchat.peripheral"
|
||||||
|
|
||||||
// Battery optimizer integration
|
// Battery optimizer integration
|
||||||
private let batteryOptimizer = BatteryOptimizer.shared
|
private let batteryOptimizer = BatteryOptimizer.shared
|
||||||
private var batteryOptimizerCancellables = Set<AnyCancellable>()
|
private var batteryOptimizerCancellables = Set<AnyCancellable>()
|
||||||
@@ -1498,8 +1509,17 @@ class BluetoothMeshService: NSObject {
|
|||||||
// Set up queue-specific key for deadlock prevention
|
// Set up queue-specific key for deadlock prevention
|
||||||
collectionsQueue.setSpecific(key: collectionsQueueKey, value: ())
|
collectionsQueue.setSpecific(key: collectionsQueueKey, value: ())
|
||||||
|
|
||||||
centralManager = CBCentralManager(delegate: self, queue: nil)
|
// Initialize Bluetooth managers with state restoration
|
||||||
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
|
centralManager = CBCentralManager(
|
||||||
|
delegate: self,
|
||||||
|
queue: nil,
|
||||||
|
options: [CBCentralManagerOptionRestoreIdentifierKey: Self.centralManagerRestorationID]
|
||||||
|
)
|
||||||
|
peripheralManager = CBPeripheralManager(
|
||||||
|
delegate: self,
|
||||||
|
queue: nil,
|
||||||
|
options: [CBPeripheralManagerOptionRestoreIdentifierKey: Self.peripheralManagerRestorationID]
|
||||||
|
)
|
||||||
|
|
||||||
// Setup app state notifications
|
// Setup app state notifications
|
||||||
setupAppStateNotifications()
|
setupAppStateNotifications()
|
||||||
@@ -1691,6 +1711,52 @@ class BluetoothMeshService: NSObject {
|
|||||||
cleanup()
|
cleanup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Background Task Management
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
private func beginBackgroundTask() -> UIBackgroundTaskIdentifier {
|
||||||
|
return UIApplication.shared.beginBackgroundTask { [weak self] in
|
||||||
|
self?.endBackgroundTask(self?.backgroundTask ?? .invalid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func endBackgroundTask(_ taskID: UIBackgroundTaskIdentifier) {
|
||||||
|
if taskID != .invalid {
|
||||||
|
UIApplication.shared.endBackgroundTask(taskID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func beginScanBackgroundTask() {
|
||||||
|
guard scanBackgroundTask == .invalid else { return }
|
||||||
|
|
||||||
|
scanBackgroundTask = UIApplication.shared.beginBackgroundTask(withName: "BluetoothScan") { [weak self] in
|
||||||
|
self?.endScanBackgroundTask()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func endScanBackgroundTask() {
|
||||||
|
if scanBackgroundTask != .invalid {
|
||||||
|
UIApplication.shared.endBackgroundTask(scanBackgroundTask)
|
||||||
|
scanBackgroundTask = .invalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func beginAdvertiseBackgroundTask() {
|
||||||
|
guard advertiseBackgroundTask == .invalid else { return }
|
||||||
|
|
||||||
|
advertiseBackgroundTask = UIApplication.shared.beginBackgroundTask(withName: "BluetoothAdvertise") { [weak self] in
|
||||||
|
self?.endAdvertiseBackgroundTask()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func endAdvertiseBackgroundTask() {
|
||||||
|
if advertiseBackgroundTask != .invalid {
|
||||||
|
UIApplication.shared.endBackgroundTask(advertiseBackgroundTask)
|
||||||
|
advertiseBackgroundTask = .invalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// MARK: - App State Management
|
// MARK: - App State Management
|
||||||
|
|
||||||
private func setupAppStateNotifications() {
|
private func setupAppStateNotifications() {
|
||||||
@@ -1707,6 +1773,18 @@ class BluetoothMeshService: NSObject {
|
|||||||
name: UIApplication.willResignActiveNotification,
|
name: UIApplication.willResignActiveNotification,
|
||||||
object: nil
|
object: nil
|
||||||
)
|
)
|
||||||
|
NotificationCenter.default.addObserver(
|
||||||
|
self,
|
||||||
|
selector: #selector(appDidEnterBackground),
|
||||||
|
name: UIApplication.didEnterBackgroundNotification,
|
||||||
|
object: nil
|
||||||
|
)
|
||||||
|
NotificationCenter.default.addObserver(
|
||||||
|
self,
|
||||||
|
selector: #selector(appWillEnterForeground),
|
||||||
|
name: UIApplication.willEnterForegroundNotification,
|
||||||
|
object: nil
|
||||||
|
)
|
||||||
#elseif os(macOS)
|
#elseif os(macOS)
|
||||||
NotificationCenter.default.addObserver(
|
NotificationCenter.default.addObserver(
|
||||||
self,
|
self,
|
||||||
@@ -1733,6 +1811,40 @@ class BluetoothMeshService: NSObject {
|
|||||||
// App will resign active
|
// App will resign active
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func appDidEnterBackground() {
|
||||||
|
isAppInForeground = false
|
||||||
|
SecureLogger.log("📱 App entered background - switching to background scanning mode", level: .info)
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
// Begin background tasks for critical operations
|
||||||
|
beginScanBackgroundTask()
|
||||||
|
beginAdvertiseBackgroundTask()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Switch to background scanning mode (continuous with battery-efficient options)
|
||||||
|
switchToBackgroundScanning()
|
||||||
|
|
||||||
|
// Update advertising for background mode
|
||||||
|
updateAdvertisingForBackgroundMode()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func appWillEnterForeground() {
|
||||||
|
isAppInForeground = true
|
||||||
|
SecureLogger.log("📱 App entering foreground - switching to foreground scanning mode", level: .info)
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
// End background tasks as app is coming to foreground
|
||||||
|
endScanBackgroundTask()
|
||||||
|
endAdvertiseBackgroundTask()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Switch to foreground scanning mode (with duty cycling)
|
||||||
|
switchToForegroundScanning()
|
||||||
|
|
||||||
|
// Update advertising for foreground mode
|
||||||
|
updateAdvertisingForForegroundMode()
|
||||||
|
}
|
||||||
|
|
||||||
private func cleanup() {
|
private func cleanup() {
|
||||||
// Send leave announcement before disconnecting
|
// Send leave announcement before disconnecting
|
||||||
sendLeaveAnnouncement()
|
sendLeaveAnnouncement()
|
||||||
@@ -1973,14 +2085,41 @@ class BluetoothMeshService: NSObject {
|
|||||||
peripheralManager?.startAdvertising(advertisementData)
|
peripheralManager?.startAdvertising(advertisementData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateAdvertisingForBackgroundMode() {
|
||||||
|
// Check if we should skip advertising in critically low battery
|
||||||
|
if batteryOptimizer.currentPowerMode == .ultraLowPower && batteryOptimizer.batteryLevel < 0.1 {
|
||||||
|
// Only skip advertising if battery is critically low (<10%)
|
||||||
|
if isAdvertising {
|
||||||
|
peripheralManager?.stopAdvertising()
|
||||||
|
isAdvertising = false
|
||||||
|
SecureLogger.log("⚡ Stopped advertising due to critically low battery", level: .info)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue advertising in background mode with battery considerations
|
||||||
|
if !isAdvertising && peripheralManager?.state == .poweredOn {
|
||||||
|
startAdvertising()
|
||||||
|
SecureLogger.log("📡 Continued advertising in background mode", level: .info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateAdvertisingForForegroundMode() {
|
||||||
|
// Always start advertising when in foreground if possible
|
||||||
|
if !isAdvertising && peripheralManager?.state == .poweredOn {
|
||||||
|
startAdvertising()
|
||||||
|
SecureLogger.log("📡 Started advertising in foreground mode", level: .info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func startScanning() {
|
func startScanning() {
|
||||||
guard centralManager?.state == .poweredOn else {
|
guard centralManager?.state == .poweredOn else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable duplicate detection
|
// Optimize scan options based on foreground/background state
|
||||||
let scanOptions: [String: Any] = [
|
let scanOptions: [String: Any] = [
|
||||||
CBCentralManagerScanOptionAllowDuplicatesKey: true
|
CBCentralManagerScanOptionAllowDuplicatesKey: isAppInForeground ? true : false
|
||||||
]
|
]
|
||||||
|
|
||||||
centralManager?.scanForPeripherals(
|
centralManager?.scanForPeripherals(
|
||||||
@@ -1991,9 +2130,11 @@ class BluetoothMeshService: NSObject {
|
|||||||
// Update scan parameters based on battery before starting
|
// Update scan parameters based on battery before starting
|
||||||
updateScanParametersForBattery()
|
updateScanParametersForBattery()
|
||||||
|
|
||||||
// Implement scan duty cycling for battery efficiency
|
// Implement scan duty cycling for battery efficiency (only in foreground)
|
||||||
|
if isAppInForeground {
|
||||||
scheduleScanDutyCycle()
|
scheduleScanDutyCycle()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func triggerRescan() {
|
func triggerRescan() {
|
||||||
guard centralManager?.state == .poweredOn else { return }
|
guard centralManager?.state == .poweredOn else { return }
|
||||||
@@ -2061,8 +2202,18 @@ class BluetoothMeshService: NSObject {
|
|||||||
func sendMessage(_ content: String, mentions: [String] = [], to recipientID: String? = nil, messageID: String? = nil, timestamp: Date? = nil) {
|
func sendMessage(_ content: String, mentions: [String] = [], to recipientID: String? = nil, messageID: String? = nil, timestamp: Date? = nil) {
|
||||||
// Defensive check for empty content
|
// Defensive check for empty content
|
||||||
guard !content.isEmpty else { return }
|
guard !content.isEmpty else { return }
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
let backgroundTask = beginBackgroundTask()
|
||||||
|
#endif
|
||||||
|
|
||||||
messageQueue.async { [weak self] in
|
messageQueue.async { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else {
|
||||||
|
#if os(iOS)
|
||||||
|
self?.endBackgroundTask(backgroundTask)
|
||||||
|
#endif
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let nickname = self.delegate as? ChatViewModel
|
let nickname = self.delegate as? ChatViewModel
|
||||||
let senderNick = nickname?.nickname ?? self.myPeerID
|
let senderNick = nickname?.nickname ?? self.myPeerID
|
||||||
@@ -2112,7 +2263,14 @@ class BluetoothMeshService: NSObject {
|
|||||||
let initialDelay = self.smartCollisionAvoidanceDelay(baseDelay: self.randomDelay())
|
let initialDelay = self.smartCollisionAvoidanceDelay(baseDelay: self.randomDelay())
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + initialDelay) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + initialDelay) { [weak self] in
|
||||||
self?.broadcastPacket(packet)
|
self?.broadcastPacket(packet)
|
||||||
|
#if os(iOS)
|
||||||
|
self?.endBackgroundTask(backgroundTask)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
#if os(iOS)
|
||||||
|
self.endBackgroundTask(backgroundTask)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2134,8 +2292,17 @@ class BluetoothMeshService: NSObject {
|
|||||||
|
|
||||||
let msgID = messageID ?? UUID().uuidString
|
let msgID = messageID ?? UUID().uuidString
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
let backgroundTask = beginBackgroundTask()
|
||||||
|
#endif
|
||||||
|
|
||||||
messageQueue.async { [weak self] in
|
messageQueue.async { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else {
|
||||||
|
#if os(iOS)
|
||||||
|
self?.endBackgroundTask(backgroundTask)
|
||||||
|
#endif
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Check if this is an old peer ID that has rotated
|
// Check if this is an old peer ID that has rotated
|
||||||
var targetPeerID = recipientPeerID
|
var targetPeerID = recipientPeerID
|
||||||
@@ -2150,6 +2317,10 @@ class BluetoothMeshService: NSObject {
|
|||||||
|
|
||||||
// Always use Noise encryption
|
// Always use Noise encryption
|
||||||
self.sendPrivateMessageViaNoise(content, to: targetPeerID, recipientNickname: recipientNickname, messageID: msgID)
|
self.sendPrivateMessageViaNoise(content, to: targetPeerID, recipientNickname: recipientNickname, messageID: msgID)
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
self.endBackgroundTask(backgroundTask)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4796,6 +4967,50 @@ extension BluetoothMeshService: CBCentralManagerDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
|
||||||
|
// Restore central manager state after app backgrounding
|
||||||
|
SecureLogger.log("🔄 Restoring CBCentralManager state", level: .info)
|
||||||
|
|
||||||
|
// Restore scanned services
|
||||||
|
if let services = dict[CBCentralManagerRestoredStateScanServicesKey] as? [CBUUID] {
|
||||||
|
SecureLogger.log("📡 Restoring scanned services: \(services)", level: .info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore scan options
|
||||||
|
if let scanOptions = dict[CBCentralManagerRestoredStateScanOptionsKey] as? [String: Any] {
|
||||||
|
SecureLogger.log("🔍 Restoring scan options: \(scanOptions)", level: .info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore peripherals
|
||||||
|
if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] {
|
||||||
|
SecureLogger.log("📱 Restoring \(peripherals.count) peripherals", level: .info)
|
||||||
|
|
||||||
|
collectionsQueue.async(flags: .barrier) { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
// Restore discovered peripherals list
|
||||||
|
self.discoveredPeripherals = peripherals
|
||||||
|
|
||||||
|
// Restore connections for connected peripherals
|
||||||
|
for peripheral in peripherals {
|
||||||
|
if peripheral.state == .connected {
|
||||||
|
// Find the peerID for this peripheral
|
||||||
|
let peerID = peripheral.identifier.uuidString
|
||||||
|
|
||||||
|
// Update our tracking
|
||||||
|
self.updatePeripheralConnection(peerID, peripheral: peripheral)
|
||||||
|
|
||||||
|
// Set delegate and discover services
|
||||||
|
peripheral.delegate = self
|
||||||
|
peripheral.discoverServices([BluetoothMeshService.serviceUUID])
|
||||||
|
|
||||||
|
SecureLogger.log("🔗 Restored connection to peer: \(peerID)", level: .info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
|
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
|
||||||
|
|
||||||
let peripheralID = peripheral.identifier.uuidString
|
let peripheralID = peripheral.identifier.uuidString
|
||||||
@@ -5311,6 +5526,33 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func peripheralManager(_ peripheral: CBPeripheralManager, willRestoreState dict: [String : Any]) {
|
||||||
|
// Restore peripheral manager state after app backgrounding
|
||||||
|
SecureLogger.log("🔄 Restoring CBPeripheralManager state", level: .info)
|
||||||
|
|
||||||
|
// Restore services
|
||||||
|
if let services = dict[CBPeripheralManagerRestoredStateServicesKey] as? [CBMutableService] {
|
||||||
|
SecureLogger.log("🛠 Restoring \(services.count) services", level: .info)
|
||||||
|
|
||||||
|
// Services are automatically restored by Core Bluetooth
|
||||||
|
// We just need to ensure our internal state is consistent
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
self?.isAdvertising = false
|
||||||
|
// Will be set to true when advertising starts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore advertisement data
|
||||||
|
if let advertisementData = dict[CBPeripheralManagerRestoredStateAdvertisementDataKey] as? [String: Any] {
|
||||||
|
SecureLogger.log("📣 Restoring advertisement data: \(advertisementData)", level: .info)
|
||||||
|
self.advertisementData = advertisementData
|
||||||
|
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
self?.isAdvertising = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {
|
func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {
|
||||||
// Service added
|
// Service added
|
||||||
}
|
}
|
||||||
@@ -5503,6 +5745,51 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|||||||
// Keeping empty implementation for compatibility
|
// Keeping empty implementation for compatibility
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func switchToBackgroundScanning() {
|
||||||
|
guard centralManager?.state == .poweredOn else { return }
|
||||||
|
|
||||||
|
// Stop existing scanning and duty cycling
|
||||||
|
centralManager?.stopScan()
|
||||||
|
scanDutyCycleTimer?.invalidate()
|
||||||
|
scanDutyCycleTimer = nil
|
||||||
|
|
||||||
|
// Use continuous scanning with battery-efficient options in background
|
||||||
|
let backgroundScanOptions: [String: Any] = [
|
||||||
|
CBCentralManagerScanOptionAllowDuplicatesKey: false // Battery efficient
|
||||||
|
]
|
||||||
|
|
||||||
|
centralManager?.scanForPeripherals(
|
||||||
|
withServices: [BluetoothMeshService.serviceUUID],
|
||||||
|
options: backgroundScanOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
SecureLogger.log("🔄 Switched to continuous background scanning", level: .info)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func switchToForegroundScanning() {
|
||||||
|
guard centralManager?.state == .poweredOn else { return }
|
||||||
|
|
||||||
|
// Stop existing scanning
|
||||||
|
centralManager?.stopScan()
|
||||||
|
scanDutyCycleTimer?.invalidate()
|
||||||
|
scanDutyCycleTimer = nil
|
||||||
|
|
||||||
|
// Use foreground scanning with duty cycling and allow duplicates for faster discovery
|
||||||
|
let foregroundScanOptions: [String: Any] = [
|
||||||
|
CBCentralManagerScanOptionAllowDuplicatesKey: true // Faster discovery
|
||||||
|
]
|
||||||
|
|
||||||
|
centralManager?.scanForPeripherals(
|
||||||
|
withServices: [BluetoothMeshService.serviceUUID],
|
||||||
|
options: foregroundScanOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
// Re-enable duty cycling for foreground operation
|
||||||
|
scheduleScanDutyCycle()
|
||||||
|
|
||||||
|
SecureLogger.log("🔄 Switched to foreground scanning with duty cycling", level: .info)
|
||||||
|
}
|
||||||
|
|
||||||
private func updateScanParametersForBattery() {
|
private func updateScanParametersForBattery() {
|
||||||
// This method is now handled by BatteryOptimizer through handlePowerModeChange
|
// This method is now handled by BatteryOptimizer through handlePowerModeChange
|
||||||
// Keeping empty implementation for compatibility
|
// Keeping empty implementation for compatibility
|
||||||
|
|||||||
@@ -184,13 +184,15 @@ class BatteryOptimizer {
|
|||||||
// When charging, use performance mode unless battery is critical
|
// When charging, use performance mode unless battery is critical
|
||||||
currentPowerMode = batteryLevel < 0.1 ? .balanced : .performance
|
currentPowerMode = batteryLevel < 0.1 ? .balanced : .performance
|
||||||
} else if isInBackground {
|
} else if isInBackground {
|
||||||
// In background, always use power saving
|
// In background, be less aggressive with power management
|
||||||
if batteryLevel < 0.2 {
|
// Don't force ultra-low power mode until battery is below 10%
|
||||||
|
// Use balanced mode in background above 30% battery
|
||||||
|
if batteryLevel < 0.1 {
|
||||||
currentPowerMode = .ultraLowPower
|
currentPowerMode = .ultraLowPower
|
||||||
} else if batteryLevel < 0.5 {
|
} else if batteryLevel < 0.3 {
|
||||||
currentPowerMode = .powerSaver
|
currentPowerMode = .powerSaver
|
||||||
} else {
|
} else {
|
||||||
currentPowerMode = .balanced
|
currentPowerMode = .balanced // Use balanced mode above 30% in background
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Foreground, not charging
|
// Foreground, not charging
|
||||||
|
|||||||
Reference in New Issue
Block a user