android: updates - no stuck Downloading state, thread-safe singleton
After a silent pre-download the state stayed Downloading, so tapping the "update ready" notification did nothing. It now returns to Available; an install request without a known update (e.g. after a process restart) re-checks and installs when found. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
e6c893219c
commit
24e536b9b9
1 file changed
+52
-6
+52
-6
@@ -116,12 +116,18 @@ class UpdateManager private constructor(private val context: Context)
|
||||
private const val UPDATES_NOTIFICATION_ID = 1001
|
||||
private const val UPDATES_PROGRESS_NOTIFICATION_ID = 1002
|
||||
|
||||
@Volatile
|
||||
private var mInstance: UpdateManager? = null
|
||||
|
||||
/* called from the main thread and the WorkManager thread: a second
|
||||
* instance would run its own check loop and state */
|
||||
@JvmStatic
|
||||
fun getInstance(context: Context): UpdateManager
|
||||
{
|
||||
return mInstance ?: UpdateManager(context.applicationContext).also { mInstance = it }
|
||||
return mInstance ?: synchronized(this)
|
||||
{
|
||||
mInstance ?: UpdateManager(context.applicationContext).also { mInstance = it }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,6 +160,10 @@ class UpdateManager private constructor(private val context: Context)
|
||||
|
||||
private var mCheckJob: Job? = null
|
||||
private var mDownloadJob: Job? = null
|
||||
/* the user asked to install (e.g. tapped "update ready") while no update
|
||||
* was known in this process: install as soon as the check finds it */
|
||||
@Volatile
|
||||
private var mInstallPending = false
|
||||
private var mVpnService: VpnStateService? = null
|
||||
|
||||
private val mConnection = object : ServiceConnection
|
||||
@@ -228,6 +238,7 @@ class UpdateManager private constructor(private val context: Context)
|
||||
check()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun check()
|
||||
{
|
||||
if (mCheckJob?.isActive == true || mDownloadJob?.isActive == true)
|
||||
@@ -236,17 +247,29 @@ class UpdateManager private constructor(private val context: Context)
|
||||
}
|
||||
mCheckJob = mScope.launch {
|
||||
mState.value = UpdateState.Checking
|
||||
val info = fetchManifest() ?: return@launch
|
||||
val info = fetchManifest() ?: run {
|
||||
mInstallPending = false
|
||||
return@launch
|
||||
}
|
||||
mPrefs.edit().putLong(Constants.PREF_LAST_UPDATE_CHECK, System.currentTimeMillis()).apply()
|
||||
if (info.versionCode > currentVersionCode())
|
||||
{
|
||||
DiagLog.i(TAG, "update: available ${info.versionName} (${info.versionCode}), " +
|
||||
"installed ${currentVersionName()} (${currentVersionCode()})")
|
||||
mState.value = UpdateState.Available(info)
|
||||
maybeDownloadSilently(info)
|
||||
if (mInstallPending)
|
||||
{
|
||||
mInstallPending = false
|
||||
downloadAndInstall()
|
||||
}
|
||||
else
|
||||
{
|
||||
maybeDownloadSilently(info)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mInstallPending = false
|
||||
DiagLog.i(TAG, "update: up to date (${currentVersionName()} ${currentVersionCode()})")
|
||||
mState.value = UpdateState.UpToDate
|
||||
}
|
||||
@@ -260,6 +283,7 @@ class UpdateManager private constructor(private val context: Context)
|
||||
* right away; otherwise a notification tells the user it is ready - one
|
||||
* tap installs it (a single system confirmation on Android < 12)
|
||||
*/
|
||||
@Synchronized
|
||||
private fun maybeDownloadSilently(info: UpdateInfo)
|
||||
{
|
||||
if (!isSilentUpdatesEnabled())
|
||||
@@ -292,7 +316,19 @@ class UpdateManager private constructor(private val context: Context)
|
||||
else
|
||||
{
|
||||
DiagLog.i(TAG, "update: ${info.versionName} downloaded, waiting for the user")
|
||||
notifyUpdateReady(info)
|
||||
/* back to Available: Downloading would block downloadAndInstall()
|
||||
* and leave the settings row stuck at 100% */
|
||||
mState.value = UpdateState.Available(info)
|
||||
if (mInstallPending)
|
||||
{
|
||||
mInstallPending = false
|
||||
UpdateInstaller.install(context, apk)
|
||||
mState.value = UpdateState.Idle
|
||||
}
|
||||
else
|
||||
{
|
||||
notifyUpdateReady(info)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e: Exception)
|
||||
@@ -300,6 +336,7 @@ class UpdateManager private constructor(private val context: Context)
|
||||
Log.e(TAG, "Silent update download failed", e)
|
||||
DiagLog.i(TAG, "update: silent download failed: ${e.javaClass.simpleName}: ${e.message}")
|
||||
/* the manual download from the settings screen still works */
|
||||
mState.value = UpdateState.Available(info)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -434,11 +471,20 @@ class UpdateManager private constructor(private val context: Context)
|
||||
* Downloads the update APK (with progress) and installs it as an
|
||||
* in-place update (the app data is preserved)
|
||||
*/
|
||||
@Synchronized
|
||||
fun downloadAndInstall()
|
||||
{
|
||||
val info = (mState.value as? UpdateState.Available)?.info ?: return
|
||||
if (mDownloadJob?.isActive == true)
|
||||
{
|
||||
{ /* a silent download is running: install once it is done */
|
||||
mInstallPending = true
|
||||
return
|
||||
}
|
||||
val info = (mState.value as? UpdateState.Available)?.info
|
||||
if (info == null)
|
||||
{ /* e.g. the "update ready" notification was tapped after the process
|
||||
* restarted: look the update up again and install it when found */
|
||||
mInstallPending = true
|
||||
check()
|
||||
return
|
||||
}
|
||||
mDownloadJob = mScope.launch {
|
||||
|
||||
Reference in new issue
Block a user