check for support

This commit is contained in:
CC
2026-06-10 12:28:05 +02:00
parent 4566d5d228
commit 8e96a5eb1e
8 changed files with 193 additions and 28 deletions
@@ -43,7 +43,7 @@ class BitchatApplication : Application() {
// Initialize WiFi Aware controller with persisted default
try {
val enabled = com.bitchat.android.ui.debug.DebugPreferenceManager.getWifiAwareEnabled(false)
val enabled = com.bitchat.android.ui.debug.DebugPreferenceManager.getWifiAwareEnabled(true)
com.bitchat.android.wifiaware.WifiAwareController.initialize(this, enabled)
} catch (_: Exception) { }
@@ -26,18 +26,17 @@ class PermissionManager(private val context: Context) {
private fun shouldRequireWifiAwarePermission(): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return false
val enabled = try {
com.bitchat.android.ui.debug.DebugPreferenceManager.getWifiAwareEnabled(false)
com.bitchat.android.ui.debug.DebugPreferenceManager.getWifiAwareEnabled(true)
} catch (_: Exception) {
false
true
}
if (!enabled) return false
val hasFeature = try {
context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)
return try {
com.bitchat.android.wifiaware.WifiAwareSupport.isSupported(context)
} catch (_: Exception) {
false
}
return hasFeature
}
/**
@@ -113,7 +113,7 @@ object DebugPreferenceManager {
if (ready()) prefs.edit().putBoolean(KEY_BLE_ENABLED, value).apply()
}
fun getWifiAwareEnabled(default: Boolean = false): Boolean =
fun getWifiAwareEnabled(default: Boolean = true): Boolean =
if (ready()) prefs.getBoolean(KEY_WIFI_AWARE_ENABLED, default) else default
fun setWifiAwareEnabled(value: Boolean) {
@@ -44,7 +44,7 @@ class DebugSettingsManager private constructor() {
private val _bleEnabled = MutableStateFlow(true)
val bleEnabled: StateFlow<Boolean> = _bleEnabled.asStateFlow()
private val _wifiAwareEnabled = MutableStateFlow(false)
private val _wifiAwareEnabled = MutableStateFlow(true)
val wifiAwareEnabled: StateFlow<Boolean> = _wifiAwareEnabled.asStateFlow()
// Master transport toggles
@@ -76,7 +76,7 @@ class DebugSettingsManager private constructor() {
_maxClientConnections.value = DebugPreferenceManager.getMaxConnectionsClient(8)
// Transport toggles
_bleEnabled.value = DebugPreferenceManager.getBleEnabled(true)
_wifiAwareEnabled.value = DebugPreferenceManager.getWifiAwareEnabled(false)
_wifiAwareEnabled.value = DebugPreferenceManager.getWifiAwareEnabled(true)
_wifiAwareVerbose.value = DebugPreferenceManager.getWifiAwareVerbose(false)
} catch (_: Exception) {
// Preferences not ready yet; keep defaults. They will be applied on first change.
@@ -130,6 +130,9 @@ fun DebugSettingsSheet(
val wifiAwareVerbose by manager.wifiAwareVerbose.collectAsState()
val wifiAwareDiscovered by manager.wifiAwareDiscovered.collectAsState()
val wifiAwareConnected by manager.wifiAwareConnected.collectAsState()
val wifiAwareSupported by com.bitchat.android.wifiaware.WifiAwareController.supported.collectAsState()
val wifiAwareAvailable by com.bitchat.android.wifiaware.WifiAwareController.available.collectAsState()
val wifiAwareSupportStatus by com.bitchat.android.wifiaware.WifiAwareController.supportStatus.collectAsState()
// Persistent notification is now controlled solely by MeshServicePreferences.isBackgroundEnabled
val listState = rememberLazyListState()
val isScrolled by remember {
@@ -327,7 +330,23 @@ fun DebugSettingsSheet(
Icon(Icons.Filled.Wifi, contentDescription = null, tint = Color(0xFF9C27B0))
Spacer(Modifier.width(8.dp))
Text("WiFi Aware", fontFamily = FontFamily.Monospace, modifier = Modifier.weight(1f))
Switch(checked = wifiAwareEnabled, onCheckedChange = { manager.setWifiAwareEnabled(it) })
val wifiSwitchEnabled = wifiAwareSupported
Text(
when {
!wifiAwareSupported -> "unsupported"
wifiAwareAvailable -> "available"
else -> "unavailable"
},
fontFamily = FontFamily.Monospace,
fontSize = 11.sp,
color = colorScheme.onSurface.copy(alpha = 0.6f)
)
Spacer(Modifier.width(8.dp))
Switch(
checked = wifiAwareEnabled && wifiAwareSupported,
enabled = wifiSwitchEnabled,
onCheckedChange = { manager.setWifiAwareEnabled(it) }
)
}
Row(verticalAlignment = Alignment.CenterVertically) {
Spacer(Modifier.width(24.dp))
@@ -601,17 +620,39 @@ fun DebugSettingsSheet(
item {
Surface(shape = RoundedCornerShape(12.dp), color = colorScheme.surfaceVariant.copy(alpha = 0.2f)) {
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) {
val running by com.bitchat.android.wifiaware.WifiAwareController.running.collectAsState()
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Icon(Icons.Filled.WifiTethering, contentDescription = null, tint = Color(0xFF9C27B0))
Text("WiFi Aware", fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Medium)
Spacer(Modifier.weight(1f))
val running by com.bitchat.android.wifiaware.WifiAwareController.running.collectAsState()
Text(if (running) "running" else "stopped", fontFamily = FontFamily.Monospace, fontSize = 12.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
val wifiStatusText = when {
!wifiAwareSupported -> "unsupported"
running -> "running"
!wifiAwareAvailable -> "unavailable"
else -> "stopped"
}
Text(wifiStatusText, fontFamily = FontFamily.Monospace, fontSize = 12.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
}
if (!wifiAwareSupported) {
Text(
wifiAwareSupportStatus?.reason ?: "Wi-Fi Aware is not supported on this device",
fontFamily = FontFamily.Monospace,
fontSize = 11.sp,
color = colorScheme.onSurface.copy(alpha = 0.6f)
)
}
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
AssistChip(onClick = { manager.setWifiAwareEnabled(true) }, label = { Text("Start") })
AssistChip(
onClick = { manager.setWifiAwareEnabled(true) },
enabled = wifiAwareSupported,
label = { Text("Start") }
)
AssistChip(onClick = { manager.setWifiAwareEnabled(false) }, label = { Text("Stop") })
AssistChip(onClick = { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendBroadcastAnnounce() }, label = { Text("Announce") })
AssistChip(
onClick = { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendBroadcastAnnounce() },
enabled = running,
label = { Text("Announce") }
)
}
Text("Discovered: ${wifiAwareDiscovered.size}", fontFamily = FontFamily.Monospace, fontSize = 12.sp)
if (wifiAwareDiscovered.isEmpty()) {
@@ -30,12 +30,22 @@ object WifiAwareController {
private var starting = false
private val restartInFlight = AtomicBoolean(false)
private var awareReceiverRegistered = false
private var lastBlockedReason: String? = null
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val _enabled = MutableStateFlow(false)
val enabled: StateFlow<Boolean> = _enabled.asStateFlow()
private val _supported = MutableStateFlow(false)
val supported: StateFlow<Boolean> = _supported.asStateFlow()
private val _available = MutableStateFlow(false)
val available: StateFlow<Boolean> = _available.asStateFlow()
private val _supportStatus = MutableStateFlow<WifiAwareSupport.Status?>(null)
val supportStatus: StateFlow<WifiAwareSupport.Status?> = _supportStatus.asStateFlow()
private val _running = MutableStateFlow(false)
val running: StateFlow<Boolean> = _running.asStateFlow()
@@ -51,7 +61,12 @@ object WifiAwareController {
fun initialize(context: Context, enabledByDefault: Boolean) {
appContext = context.applicationContext
registerAwareStateReceiver(appContext!!)
val status = refreshSupportStatus(appContext!!)
if (status.supported) {
registerAwareStateReceiver(appContext!!)
} else {
Log.i(TAG, "Wi-Fi Aware unsupported: ${status.reason}")
}
setEnabled(enabledByDefault)
// Start background poller for debug surfacing
scope.launch {
@@ -95,16 +110,20 @@ object WifiAwareController {
synchronized(lifecycleLock) { starting = false }
return
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
Log.w(TAG, "WiFi Aware requires Android 10 (Q)+; disabled.")
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("WiFi Aware not supported on this device (requires Android 10+)")) } catch (_: Exception) {}
val status = refreshSupportStatus(ctx)
if (!status.supported) {
val reason = status.reason ?: "not supported"
Log.w(TAG, "WiFi Aware unsupported; not starting ($reason)")
addBlockedDebugMessage("unsupported:$reason", "Wi-Fi Aware not supported on this device ($reason)")
synchronized(lifecycleLock) { starting = false }
return
}
val awareManager = ctx.getSystemService(android.net.wifi.aware.WifiAwareManager::class.java)
if (awareManager == null || !awareManager.isAvailable) {
val awareManager = WifiAwareSupport.getManager(ctx)
if (awareManager == null || !status.available) {
Log.w(TAG, "Wi-Fi Aware is not currently available; not starting")
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("WiFi Aware is not available on this device right now")) } catch (_: Exception) {}
addBlockedDebugMessage("unavailable", "Wi-Fi Aware is not available right now")
synchronized(lifecycleLock) { starting = false }
return
}
@@ -121,7 +140,7 @@ object WifiAwareController {
if (!locationEnabled) {
Log.w(TAG, "Location services are disabled; Wi-Fi Aware cannot start.")
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Enable Location Services to start Wi-Fi Aware")) } catch (_: Exception) {}
addBlockedDebugMessage("location-disabled", "Enable Location Services to start Wi-Fi Aware")
synchronized(lifecycleLock) { starting = false }
return
}
@@ -131,7 +150,7 @@ object WifiAwareController {
val granted = androidx.core.content.ContextCompat.checkSelfPermission(ctx, android.Manifest.permission.NEARBY_WIFI_DEVICES) == android.content.pm.PackageManager.PERMISSION_GRANTED
if (!granted) {
Log.w(TAG, "Missing NEARBY_WIFI_DEVICES permission; not starting WiFi Aware")
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Grant Nearby WiFi Devices to start WiFi Aware")) } catch (_: Exception) {}
addBlockedDebugMessage("missing-nearby-wifi", "Grant Nearby Wi-Fi Devices to start Wi-Fi Aware")
synchronized(lifecycleLock) { starting = false }
return
}
@@ -152,6 +171,7 @@ object WifiAwareController {
_running.value = true
}
try { com.bitchat.android.service.MeshServiceHolder.unifiedMeshService?.refreshDelegates() } catch (_: Exception) { }
clearBlockedDebugMessage()
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("WiFi Aware started")) } catch (_: Exception) {}
} else {
if (reusableService == null) {
@@ -216,6 +236,8 @@ object WifiAwareController {
if (delayMs > 0L) delay(delayMs)
var attempt = 0
while (_enabled.value && !_running.value && attempt < MAX_RESTART_ATTEMPTS) {
val ctx = appContext
if (ctx != null && !refreshSupportStatus(ctx).supported) break
startIfPossible()
if (_running.value) break
attempt++
@@ -234,17 +256,16 @@ object WifiAwareController {
*/
private fun registerAwareStateReceiver(ctx: Context) {
if (awareReceiverRegistered) return
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return
if (!refreshSupportStatus(ctx).supported) return
try {
val filter = android.content.IntentFilter(
android.net.wifi.aware.WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED
)
ctx.registerReceiver(object : android.content.BroadcastReceiver() {
override fun onReceive(c: Context?, intent: android.content.Intent?) {
val mgr = ctx.getSystemService(android.net.wifi.aware.WifiAwareManager::class.java)
val available = mgr?.isAvailable == true
Log.i(TAG, "Wi-Fi Aware availability changed: available=$available enabled=${_enabled.value} running=${_running.value}")
if (available) {
val status = refreshSupportStatus(ctx)
Log.i(TAG, "Wi-Fi Aware availability changed: supported=${status.supported} available=${status.available} enabled=${_enabled.value} running=${_running.value}")
if (status.available) {
if (_enabled.value) restartIfStillEnabled(500)
} else if (_running.value) {
// Aware went away; tear down cleanly so we can re-attach when it returns.
@@ -259,5 +280,26 @@ object WifiAwareController {
}
}
private fun refreshSupportStatus(ctx: Context): WifiAwareSupport.Status {
val status = WifiAwareSupport.evaluate(ctx)
_supported.value = status.supported
_available.value = status.available
_supportStatus.value = status
return status
}
private fun addBlockedDebugMessage(key: String, message: String) {
if (lastBlockedReason == key) return
lastBlockedReason = key
try {
com.bitchat.android.ui.debug.DebugSettingsManager.getInstance()
.addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage(message))
} catch (_: Exception) { }
}
private fun clearBlockedDebugMessage() {
lastBlockedReason = null
}
fun getService(): WifiAwareMeshService? = service
}
@@ -318,6 +318,15 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
Log.i(TAG, "Wi-Fi Aware transport disabled by debug settings; not starting")
return
}
val supportStatus = com.bitchat.android.wifiaware.WifiAwareSupport.evaluate(context)
if (!supportStatus.supported) {
Log.i(TAG, "Wi-Fi Aware unsupported on this device; not starting (${supportStatus.reason})")
return
}
if (!supportStatus.available) {
Log.i(TAG, "Wi-Fi Aware unavailable right now; not starting (${supportStatus.reason})")
return
}
if (recoveryInProgress) {
Log.i(TAG, "Wi-Fi Aware recovery cleanup still in progress; deferring start")
return
@@ -0,0 +1,74 @@
package com.bitchat.android.wifiaware
import android.content.Context
import android.content.pm.PackageManager
import android.net.wifi.aware.WifiAwareManager
import android.os.Build
/**
* Centralized Wi-Fi Aware capability checks.
*
* "Supported" is stable device/API capability. "Available" is runtime state and can change
* when Wi-Fi, location, airplane mode, or system radio state changes.
*/
object WifiAwareSupport {
data class Status(
val supported: Boolean,
val available: Boolean,
val reason: String? = null
)
fun evaluate(context: Context): Status {
val appContext = context.applicationContext
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
return Status(
supported = false,
available = false,
reason = "requires Android 10+"
)
}
val hasFeature = try {
appContext.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)
} catch (_: Exception) {
false
}
if (!hasFeature) {
return Status(
supported = false,
available = false,
reason = "device does not advertise Wi-Fi Aware support"
)
}
val manager = getManager(appContext)
?: return Status(
supported = false,
available = false,
reason = "WifiAwareManager unavailable"
)
val available = try {
manager.isAvailable
} catch (_: Exception) {
false
}
return Status(
supported = true,
available = available,
reason = if (available) null else "Wi-Fi Aware temporarily unavailable"
)
}
fun isSupported(context: Context): Boolean = evaluate(context).supported
fun getManager(context: Context): WifiAwareManager? {
return try {
context.applicationContext.getSystemService(WifiAwareManager::class.java)
} catch (_: Exception) {
null
}
}
}