fix(android): declare WAKE_LOCK — the stream's Wi-Fi locks never actually engaged
WifiLock.acquire() enforces the WAKE_LOCK permission, which the manifest never declared — every acquisition since the first Wi-Fi lock shipped threw SecurityException, silently swallowed by a bare runCatching. The phone's own accounting proved it (dumpsys wifi: high_perf/low_latency active_time_ms = 0 across weeks of streams): every on-device session ran with Wi-Fi power save fully active, whatever the code intended. Verified live after the fix: both locks registered in WifiLockManager, mPowerSaveDisableRequests=2, ping RTT to the streaming phone 3.8 ms avg. A failed acquire now logs loudly — this class of failure must never be invisible again. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,12 @@
|
||||
reception needs it (also an OEM Wi-Fi power-save hedge). -->
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<!-- WifiLock.acquire() ENFORCES this (a normal permission, granted at install). Without it the
|
||||
stream's Wi-Fi locks throw SecurityException and power save stays on: downlink delivery
|
||||
clumps at beacon intervals — hundreds of ms of latency mush + periodic whole-frame loss.
|
||||
Its absence went unnoticed for weeks because the acquire was wrapped in a silent
|
||||
runCatching (now logged). -->
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<!-- Enforced from Android 17 (SDK 37) for ALL local-network traffic incl. the QUIC socket.
|
||||
Harmless to declare on earlier releases. -->
|
||||
<uses-permission android:name="android.permission.ACCESS_LOCAL_NETWORK" />
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.pm.ActivityInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.wifi.WifiManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.view.SurfaceHolder
|
||||
import android.view.SurfaceView
|
||||
import android.view.WindowManager
|
||||
@@ -126,8 +127,10 @@ fun StreamScreen(handle: Long, micEnabled: Boolean, onDisconnect: () -> Unit) {
|
||||
// - FULL_HIGH_PERF covers older releases — it is deprecated AND a documented no-op on recent
|
||||
// Android, which is exactly why it can't be the only lock (a lesson learned: holding just
|
||||
// HIGH_PERF left power save fully active on Android 13+).
|
||||
// Needs no permission beyond ACCESS_WIFI_STATE (declared). Non-reference-counted: one explicit
|
||||
// acquire/release each.
|
||||
// acquire() ENFORCES the WAKE_LOCK permission (manifest) — and a failed acquire MUST be loud:
|
||||
// a silent runCatching hid the missing permission for weeks (dumpsys wifi showed
|
||||
// low_latency_active_time_ms=0 across every "locked" stream). Non-reference-counted: one
|
||||
// explicit acquire/release each.
|
||||
val wifiLocks = remember(handle) {
|
||||
val wm = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as? WifiManager
|
||||
?: return@remember emptyList<WifiManager.WifiLock>()
|
||||
@@ -144,7 +147,11 @@ fun StreamScreen(handle: Long, micEnabled: Boolean, onDisconnect: () -> Unit) {
|
||||
|
||||
DisposableEffect(handle) {
|
||||
window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
wifiLocks.forEach { runCatching { it.acquire() } }
|
||||
wifiLocks.forEach { lock ->
|
||||
runCatching { lock.acquire() }.onFailure { e ->
|
||||
Log.w("punktfunk", "WifiLock acquire failed — power save stays ON: $lock", e)
|
||||
}
|
||||
}
|
||||
// HDMI Auto Low-Latency Mode: ask the display to drop its post-processing (game mode) —
|
||||
// the biggest panel-side latency win on the TV boxes. No-op where ALLM isn't supported. API
|
||||
// 30+. Part of the experimental low-latency stack.
|
||||
|
||||
Reference in New Issue
Block a user