scan duration fix (#557)

This commit is contained in:
callebtc
2026-01-05 16:41:41 +07:00
committed by GitHub
parent 171483a1da
commit eef831fe60
7 changed files with 47 additions and 27 deletions
+1
View File
@@ -84,6 +84,7 @@ dependencies {
// Lifecycle // Lifecycle
implementation(libs.bundles.lifecycle) implementation(libs.bundles.lifecycle)
implementation(libs.androidx.lifecycle.process)
// Navigation // Navigation
implementation(libs.androidx.navigation.compose) implementation(libs.androidx.navigation.compose)
@@ -721,9 +721,6 @@ class MainActivity : OrientationAwareActivity() {
if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) { if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) {
// Reattach mesh delegate to new ChatViewModel instance after Activity recreation // Reattach mesh delegate to new ChatViewModel instance after Activity recreation
try { meshService.delegate = chatViewModel } catch (_: Exception) { } try { meshService.delegate = chatViewModel } catch (_: Exception) { }
// Set app foreground state
meshService.connectionManager.setAppBackgroundState(false)
chatViewModel.setAppBackgroundState(false)
// Check if Bluetooth was disabled while app was backgrounded // Check if Bluetooth was disabled while app was backgrounded
val currentBluetoothStatus = bluetoothStatusManager.checkBluetoothStatus() val currentBluetoothStatus = bluetoothStatusManager.checkBluetoothStatus()
@@ -750,9 +747,6 @@ class MainActivity : OrientationAwareActivity() {
super.onPause() super.onPause()
// Only set background state if app is fully initialized // Only set background state if app is fully initialized
if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) { if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) {
// Set app background state
meshService.connectionManager.setAppBackgroundState(true)
chatViewModel.setAppBackgroundState(true)
// Detach UI delegate so the foreground service can own DM notifications while UI is closed // Detach UI delegate so the foreground service can own DM notifications while UI is closed
try { meshService.delegate = null } catch (_: Exception) { } try { meshService.delegate = null } catch (_: Exception) { }
} }
@@ -247,13 +247,6 @@ class BluetoothConnectionManager(
return active return active
} }
/**
* Set app background state for power optimization
*/
fun setAppBackgroundState(inBackground: Boolean) {
powerManager.setAppBackgroundState(inBackground)
}
/** /**
* Broadcast packet to connected devices with connection limit enforcement * Broadcast packet to connected devices with connection limit enforcement
* Automatically fragments large packets to fit within BLE MTU limits * Automatically fragments large packets to fit within BLE MTU limits
@@ -7,7 +7,13 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.os.BatteryManager import android.os.BatteryManager
import android.os.Handler
import android.os.Looper
import android.util.Log import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlin.math.max import kotlin.math.max
@@ -15,7 +21,7 @@ import kotlin.math.max
* Power-aware Bluetooth management for bitchat * Power-aware Bluetooth management for bitchat
* Adjusts scanning, advertising, and connection behavior based on battery state * Adjusts scanning, advertising, and connection behavior based on battery state
*/ */
class PowerManager(private val context: Context) { class PowerManager(private val context: Context) : LifecycleEventObserver {
companion object { companion object {
private const val TAG = "PowerManager" private const val TAG = "PowerManager"
@@ -49,7 +55,7 @@ class PowerManager(private val context: Context) {
private var currentMode = PowerMode.BALANCED private var currentMode = PowerMode.BALANCED
private var isCharging = false private var isCharging = false
private var batteryLevel = 100 private var batteryLevel = 100
private var isAppInBackground = false private var isAppInBackground = true
private val powerScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) private val powerScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private var dutyCycleJob: Job? = null private var dutyCycleJob: Job? = null
@@ -87,6 +93,16 @@ class PowerManager(private val context: Context) {
init { init {
registerBatteryReceiver() registerBatteryReceiver()
// Register for process lifecycle events on the main thread
Handler(Looper.getMainLooper()).post {
try {
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
} catch (e: Exception) {
Log.e(TAG, "Failed to register lifecycle observer: ${e.message}")
}
}
updatePowerMode() updatePowerMode()
} }
@@ -99,13 +115,30 @@ class PowerManager(private val context: Context) {
Log.i(TAG, "Stopping power management") Log.i(TAG, "Stopping power management")
powerScope.cancel() powerScope.cancel()
unregisterBatteryReceiver() unregisterBatteryReceiver()
// Unregister lifecycle observer
Handler(Looper.getMainLooper()).post {
try {
ProcessLifecycleOwner.get().lifecycle.removeObserver(this)
} catch (e: Exception) {
Log.e(TAG, "Failed to remove lifecycle observer: ${e.message}")
}
}
} }
fun setAppBackgroundState(inBackground: Boolean) { override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (isAppInBackground != inBackground) { when (event) {
isAppInBackground = inBackground Lifecycle.Event.ON_START -> {
Log.d(TAG, "App background state changed: $inBackground") Log.d(TAG, "Process lifecycle: ON_START (App coming to foreground)")
updatePowerMode() isAppInBackground = false
updatePowerMode()
}
Lifecycle.Event.ON_STOP -> {
Log.d(TAG, "Process lifecycle: ON_STOP (App going to background)")
isAppInBackground = true
updatePowerMode()
}
else -> {}
} }
} }
@@ -133,7 +166,7 @@ class PowerManager(private val context: Context) {
.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT) .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
PowerMode.ULTRA_LOW_POWER -> builder PowerMode.ULTRA_LOW_POWER -> builder
.setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC) .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.setMatchMode(ScanSettings.MATCH_MODE_STICKY) .setMatchMode(ScanSettings.MATCH_MODE_STICKY)
.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT) .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
} }
@@ -750,10 +750,6 @@ class ChatViewModel(
return meshService.getDebugStatus() return meshService.getDebugStatus()
} }
fun setAppBackgroundState(inBackground: Boolean) {
notificationManager.setAppBackgroundState(inBackground)
}
fun setCurrentPrivateChatPeer(peerID: String?) { fun setCurrentPrivateChatPeer(peerID: String?) {
notificationManager.setCurrentPrivateChatPeer(peerID) notificationManager.setCurrentPrivateChatPeer(peerID)
} }
@@ -80,9 +80,9 @@ object AppConstants {
const val SCAN_ON_DURATION_NORMAL_MS: Long = 8_000L const val SCAN_ON_DURATION_NORMAL_MS: Long = 8_000L
const val SCAN_OFF_DURATION_NORMAL_MS: Long = 2_000L const val SCAN_OFF_DURATION_NORMAL_MS: Long = 2_000L
const val SCAN_ON_DURATION_POWER_SAVE_MS: Long = 2_000L const val SCAN_ON_DURATION_POWER_SAVE_MS: Long = 2_000L
const val SCAN_OFF_DURATION_POWER_SAVE_MS: Long = 8_000L const val SCAN_OFF_DURATION_POWER_SAVE_MS: Long = 28_000L
const val SCAN_ON_DURATION_ULTRA_LOW_MS: Long = 1_000L const val SCAN_ON_DURATION_ULTRA_LOW_MS: Long = 1_000L
const val SCAN_OFF_DURATION_ULTRA_LOW_MS: Long = 10_000L const val SCAN_OFF_DURATION_ULTRA_LOW_MS: Long = 29_000L
const val MAX_CONNECTIONS_NORMAL: Int = 8 const val MAX_CONNECTIONS_NORMAL: Int = 8
const val MAX_CONNECTIONS_POWER_SAVE: Int = 8 const val MAX_CONNECTIONS_POWER_SAVE: Int = 8
const val MAX_CONNECTIONS_ULTRA_LOW: Int = 4 const val MAX_CONNECTIONS_ULTRA_LOW: Int = 4
+3
View File
@@ -61,9 +61,12 @@ mockito-inline = "4.1.0"
roboelectric = "4.15" roboelectric = "4.15"
kotlinx-coroutines-test = "1.6" kotlinx-coroutines-test = "1.6"
lifecycle-process = "2.8.7"
[libraries] [libraries]
# AndroidX Core # AndroidX Core
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "core-ktx" } androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "core-ktx" }
androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle-process" }
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle-runtime" } androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle-runtime" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activity-compose" } androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activity-compose" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }