fix(android): request battery-optimization exemption so Doze can't freeze the VPN

On phones the tunnel dropped quickly and never reconnected. Root cause: the app
had a foreground service and a wake lock but never asked to be exempt from
battery optimization, so Doze / App Standby froze the whole VPN process when the
screen went off. A frozen process means not just a dead socket but that the
in-process reconnect logic — wall-clock suspend detection, the NetworkChanged
handler, keepalive — never runs at all, which is exactly "reconnect doesn't
work."

Adds the REQUEST_IGNORE_BATTERY_OPTIMIZATIONS permission and, on VPN start,
prompts for the exemption when it isn't already granted (checked via
isIgnoringBatteryOptimizations, so it asks once). Fired after the VPN consent so
the two system dialogs don't stack. Socket protection on reconnect was already
correct (try_connect_transport protects every new socket), so this targets the
process-freeze, which is the part that stopped recovery.
This commit is contained in:
ospab 2026-08-29 00:36:20 +03:00
parent 9cb723cacb
commit e7a750dd77
2 changed files with 23 additions and 0 deletions

View File

@ -7,6 +7,10 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- Without a battery-optimization exemption, Doze freezes the VPN service
when the screen is off: the connection drops and the in-process
reconnect logic never runs because the whole process is suspended. -->
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<application <application
android:label="ostp_client" android:label="ostp_client"
android:name="${applicationName}" android:name="${applicationName}"

View File

@ -152,5 +152,24 @@ class MainActivity : FlutterActivity() {
intent.putExtra("configJson", pendingConfigJson) intent.putExtra("configJson", pendingConfigJson)
} }
androidx.core.content.ContextCompat.startForegroundService(this, intent) androidx.core.content.ContextCompat.startForegroundService(this, intent)
// Ask to be exempt from battery optimization so Doze does not freeze the
// service (which drops the tunnel AND stops the in-process reconnect from
// ever running). Only prompts if not already exempt; runs after the VPN
// consent so the two system dialogs do not stack.
requestBatteryExemptionIfNeeded()
}
private fun requestBatteryExemptionIfNeeded() {
try {
val pm = getSystemService(android.content.Context.POWER_SERVICE) as android.os.PowerManager
if (!pm.isIgnoringBatteryOptimizations(packageName)) {
val intent = Intent(android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
intent.data = android.net.Uri.parse("package:$packageName")
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
} catch (e: Throwable) {
android.util.Log.e("MainActivity", "Battery exemption request failed", e)
}
} }
} }