android: updates - honour the charging constraint, real metered check
- drop the in-process 12 h loop: the VPN keeps the process alive, so it checked and pre-downloaded on battery, defeating the worker constraint - the worker now waits for the check and a silent download it starts - metered is judged by the physical network: the VPN reports itself unmetered, so pre-downloads ran over cellular while connected Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6668dcfb56
commit
5f8b813eb4
2 files changed
+41
-17
No files matched your search
+6
-1
@@ -29,7 +29,12 @@ class UpdateCheckWorker(context: Context, params: WorkerParameters) : CoroutineW
|
||||
{
|
||||
override suspend fun doWork(): Result
|
||||
{
|
||||
UpdateManager.getInstance(applicationContext).checkIfDue()
|
||||
/* wait for the check (and a silent pre-download it starts): returning
|
||||
* early lets the system reclaim the process mid-request, outside the
|
||||
* worker's charging/network constraints */
|
||||
val manager = UpdateManager.getInstance(applicationContext)
|
||||
manager.checkIfDue()?.join()
|
||||
manager.awaitDownload()
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
|
||||
+35
-16
@@ -26,6 +26,7 @@ import android.content.SharedPreferences
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
@@ -41,7 +42,6 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -51,6 +51,7 @@ import org.strongswan.android.R
|
||||
import org.strongswan.android.logic.DiagLog
|
||||
import org.strongswan.android.logic.StrongSwanApplication
|
||||
import org.strongswan.android.logic.VpnStateService
|
||||
import org.strongswan.android.logic.connectivity.UnderlyingNetwork
|
||||
import org.strongswan.android.ui.MainActivity
|
||||
import org.strongswan.android.utils.Constants
|
||||
import java.io.File
|
||||
@@ -198,13 +199,16 @@ class UpdateManager private constructor(private val context: Context)
|
||||
.build()
|
||||
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
|
||||
UpdateCheckWorker.WORK_NAME, ExistingPeriodicWorkPolicy.UPDATE, request)
|
||||
mScope.launch {
|
||||
while (true)
|
||||
{
|
||||
checkIfDue()
|
||||
delay(Constants.UPDATE_CHECK_INTERVAL_MS)
|
||||
}
|
||||
}
|
||||
/* no in-process timer: the VPN service keeps the process alive, so a
|
||||
* loop here would check (and pre-download) on battery every 12 h and
|
||||
* defeat the worker's charging constraint. The worker and the check
|
||||
* on app resume cover it. */
|
||||
}
|
||||
|
||||
/** Waits for a running silent download (used by the worker). */
|
||||
suspend fun awaitDownload()
|
||||
{
|
||||
mDownloadJob?.join()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,17 +216,18 @@ class UpdateManager private constructor(private val context: Context)
|
||||
* last-check timestamp). Called by the periodic worker, the in-process
|
||||
* loop and on app resume.
|
||||
*/
|
||||
fun checkIfDue()
|
||||
fun checkIfDue(): Job?
|
||||
{
|
||||
if (!isAutoCheckEnabled() || mCheckJob?.isActive == true || mDownloadJob?.isActive == true)
|
||||
{
|
||||
return
|
||||
return null
|
||||
}
|
||||
val last = mPrefs.getLong(Constants.PREF_LAST_UPDATE_CHECK, 0L)
|
||||
if (System.currentTimeMillis() - last >= Constants.UPDATE_CHECK_INTERVAL_MS)
|
||||
{
|
||||
check()
|
||||
return check()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun isAutoCheckEnabled(): Boolean
|
||||
@@ -239,13 +244,13 @@ class UpdateManager private constructor(private val context: Context)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun check()
|
||||
private fun check(): Job?
|
||||
{
|
||||
if (mCheckJob?.isActive == true || mDownloadJob?.isActive == true)
|
||||
{
|
||||
return
|
||||
return mCheckJob?.takeIf { it.isActive }
|
||||
}
|
||||
mCheckJob = mScope.launch {
|
||||
val job = mScope.launch {
|
||||
mState.value = UpdateState.Checking
|
||||
val info = fetchManifest() ?: run {
|
||||
mInstallPending = false
|
||||
@@ -274,6 +279,8 @@ class UpdateManager private constructor(private val context: Context)
|
||||
mState.value = UpdateState.UpToDate
|
||||
}
|
||||
}
|
||||
mCheckJob = job
|
||||
return job
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -348,8 +355,20 @@ class UpdateManager private constructor(private val context: Context)
|
||||
|
||||
private fun isNetworkMetered(): Boolean
|
||||
{
|
||||
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
|
||||
return cm?.isActiveNetworkMetered ?: true
|
||||
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager ?: return true
|
||||
/* the VPN marks itself unmetered (setMetered(false)), so with the tunnel
|
||||
* up the active network always looks unmetered - judge by the physical
|
||||
* network below it instead */
|
||||
val underlying = UnderlyingNetwork.resolve(context) ?: return cm.isActiveNetworkMetered
|
||||
val caps = try
|
||||
{
|
||||
cm.getNetworkCapabilities(underlying)
|
||||
}
|
||||
catch (_: Exception)
|
||||
{
|
||||
null
|
||||
} ?: return true
|
||||
return !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
|
||||
}
|
||||
|
||||
private fun isVpnDown(): Boolean
|
||||
|
||||
Reference in new issue
Block a user