Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,18 @@
</intent-filter>
</receiver>

<!-- Keeps the app process alive while a wine session is paused in the
background or while a component download/install is in flight, so
screen lock does not kill the container or interrupt the install.
stopWithTask=false lets us see onTaskRemoved on user swipe-away,
which is the only path that should tear the session down. -->
<service
android:name="com.winlator.cmod.runtime.system.SessionKeepAliveService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="dataSync"
android:stopWithTask="false" />

<service
android:name="com.winlator.cmod.feature.stores.steam.service.SteamService"
android:enabled="true"
Expand Down
71 changes: 46 additions & 25 deletions app/src/main/feature/settings/contents/ContentsFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -510,14 +510,25 @@ class ContentsFragment : Fragment() {
)
}

// Hold the keep-alive across the extraction/install so screen lock
// doesn't kill the process mid-extract. The callback above can chain
// into finishInstallContent() asynchronously; we release after the
// install pipeline finishes (success, failure, or terminal callback).
val installKeepAliveTag = "components_install_${uri}_${System.currentTimeMillis()}"
val appCtx = requireContext().applicationContext
com.winlator.cmod.runtime.system.SessionKeepAliveService.startDownload(appCtx, installKeepAliveTag)
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
runCatching { manager.extraContentFile(uri, callback, extractionProgress) }
.onFailure {
runOnMain {
clearDownloadProgress()
AppUtils.showToast(requireContext(), R.string.input_controls_editor_unable_to_import)
try {
runCatching { manager.extraContentFile(uri, callback, extractionProgress) }
.onFailure {
runOnMain {
clearDownloadProgress()
AppUtils.showToast(requireContext(), R.string.input_controls_editor_unable_to_import)
}
}
}
} finally {
com.winlator.cmod.runtime.system.SessionKeepAliveService.stopDownload(appCtx, installKeepAliveTag)
}
}
}

Expand All @@ -543,6 +554,12 @@ class ContentsFragment : Fragment() {
indeterminate = true,
)

// Keep the app process alive while the download/install runs so screen
// lock doesn't tear it down. installSelectedContent() owns its own
// keep-alive scope; this one covers the download step alone.
val keepAliveTag = "components_download_${remoteUrl}"
val appCtx = requireContext().applicationContext
com.winlator.cmod.runtime.system.SessionKeepAliveService.startDownload(appCtx, keepAliveTag)
viewLifecycleOwner.lifecycleScope.launch {
val output = File(requireContext().cacheDir, "temp_${System.currentTimeMillis()}")
val success =
Expand All @@ -567,26 +584,30 @@ class ContentsFragment : Fragment() {
}
}

if (!isAdded || view == null) {
output.delete()
clearDownloadProgress()
return@launch
}
try {
if (!isAdded || view == null) {
output.delete()
clearDownloadProgress()
return@launch
}

if (success) {
updateDownloadProgress(
title = getString(R.string.settings_content_extracting_title),
message = profile.verName,
indeterminate = true,
)
installSelectedContent(
Uri.parse(output.absolutePath),
getString(R.string.settings_content_download_complete),
remoteUrl,
)
} else if (isAdded) {
clearDownloadProgress()
AppUtils.showToast(requireContext(), R.string.settings_content_download_failed)
if (success) {
updateDownloadProgress(
title = getString(R.string.settings_content_extracting_title),
message = profile.verName,
indeterminate = true,
)
installSelectedContent(
Uri.parse(output.absolutePath),
getString(R.string.settings_content_download_complete),
remoteUrl,
)
} else if (isAdded) {
clearDownloadProgress()
AppUtils.showToast(requireContext(), R.string.settings_content_download_failed)
}
} finally {
com.winlator.cmod.runtime.system.SessionKeepAliveService.stopDownload(appCtx, keepAliveTag)
}
}
}
Expand Down
84 changes: 51 additions & 33 deletions app/src/main/feature/settings/drivers/DriversFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,11 @@ class DriversFragment : Fragment() {
)
publishState()

// Hold the app process alive across this download so screen lock can't
// interrupt it. installDriverPackage owns its own keep-alive scope.
val keepAliveTag = "drivers_download_${asset.downloadUrl}"
val appCtx = requireContext().applicationContext
com.winlator.cmod.runtime.system.SessionKeepAliveService.startDownload(appCtx, keepAliveTag)
viewLifecycleOwner.lifecycleScope.launch {
val output = File(requireContext().cacheDir, "driver_${System.currentTimeMillis()}.zip")
val success =
Expand Down Expand Up @@ -469,24 +474,28 @@ class DriversFragment : Fragment() {
}
}

if (!isAdded || view == null) {
output.delete()
clearDownloadProgress()
return@launch
}
try {
if (!isAdded || view == null) {
output.delete()
clearDownloadProgress()
return@launch
}

if (!success) {
output.delete()
clearDownloadProgress()
AppUtils.showToast(requireContext(), R.string.settings_drivers_repo_download_failed)
return@launch
}
if (!success) {
output.delete()
clearDownloadProgress()
AppUtils.showToast(requireContext(), R.string.settings_drivers_repo_download_failed)
return@launch
}

installDriverPackage(
uri = Uri.fromFile(output),
sourceAssetName = asset.name,
onComplete = { output.delete() },
)
installDriverPackage(
uri = Uri.fromFile(output),
sourceAssetName = asset.name,
onComplete = { output.delete() },
)
} finally {
com.winlator.cmod.runtime.system.SessionKeepAliveService.stopDownload(appCtx, keepAliveTag)
}
}
}

Expand All @@ -505,31 +514,40 @@ class DriversFragment : Fragment() {
),
)

// Keep the process alive across the install (driver extraction +
// move). Released after the lifecycleScope coroutine finishes.
val installKeepAliveTag = "drivers_install_${sourceAssetName ?: uri}"
val appCtx = requireContext().applicationContext
com.winlator.cmod.runtime.system.SessionKeepAliveService.startDownload(appCtx, installKeepAliveTag)
viewLifecycleOwner.lifecycleScope.launch {
val installedDriverId =
withContext(Dispatchers.IO) {
runCatching {
adrenotoolsManager.installDriver(uri, sourceAssetName)
}.getOrDefault("")
try {
val installedDriverId =
withContext(Dispatchers.IO) {
runCatching {
adrenotoolsManager.installDriver(uri, sourceAssetName)
}.getOrDefault("")
}

if (!isAdded || view == null) {
clearDownloadProgress()
onComplete?.invoke()
return@launch
}

if (!isAdded || view == null) {
clearDownloadProgress()
onComplete?.invoke()
return@launch
}

clearDownloadProgress()
onComplete?.invoke()
if (installedDriverId.isBlank()) {
AppUtils.showToast(requireContext(), R.string.settings_drivers_install_failed)
return@launch
}

if (installedDriverId.isBlank()) {
AppUtils.showToast(requireContext(), R.string.settings_drivers_install_failed)
return@launch
SetupWizardActivity.recordInstalledDriver(requireContext(), installedDriverId)
refreshInstalledDrivers()
publishState()
} finally {
com.winlator.cmod.runtime.system.SessionKeepAliveService.stopDownload(appCtx, installKeepAliveTag)
}

SetupWizardActivity.recordInstalledDriver(requireContext(), installedDriverId)
refreshInstalledDrivers()
publishState()
}
}

Expand Down
25 changes: 25 additions & 0 deletions app/src/main/feature/setup/SetupWizardActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,11 @@ class SetupWizardActivity : FixedFontScaleFragmentActivity() {
val imageFs = ImageFs.find(this)
val rootDir = imageFs.rootDir

// Keep the process alive while the imagefs extraction runs; the user
// can lock the screen during this multi-minute step without losing it.
val keepAliveTag = "imagefs_install"
com.winlator.cmod.runtime.system.SessionKeepAliveService.startDownload(this, keepAliveTag)

Executors.newSingleThreadExecutor().execute {
try {
clearRootDir(rootDir)
Expand Down Expand Up @@ -779,6 +784,11 @@ class SetupWizardActivity : FixedFontScaleFragmentActivity() {
imageFsInstalling.value = false
wizardError.value = "ImageFS install failed: ${e.message}"
}
} finally {
com.winlator.cmod.runtime.system.SessionKeepAliveService.stopDownload(
applicationContext,
keepAliveTag,
)
}
}
}
Expand Down Expand Up @@ -1283,8 +1293,11 @@ class SetupWizardActivity : FixedFontScaleFragmentActivity() {
.filter { isRecommendedSpec(it) && it.verName !in advancedInstalledSet }
if (pending.isEmpty()) return

val keepAliveTag = "install_recommended_${System.currentTimeMillis()}"
com.winlator.cmod.runtime.system.SessionKeepAliveService.startDownload(this, keepAliveTag)
lifecycleScope.launch {
wizardError.value = null
try {
for ((index, spec) in pending.withIndex()) {
val profile =
withContext(Dispatchers.IO) {
Expand Down Expand Up @@ -1344,11 +1357,19 @@ class SetupWizardActivity : FixedFontScaleFragmentActivity() {
transferState.value = null
refreshAdvancedInstalledSet()
refreshWizardState()
} finally {
com.winlator.cmod.runtime.system.SessionKeepAliveService.stopDownload(
applicationContext,
keepAliveTag,
)
}
}
}

private fun installAdvancedComponent(spec: RemotePackageSpec) {
if (transferState.value != null) return
val keepAliveTag = "install_advanced_${spec.remoteUrl}"
com.winlator.cmod.runtime.system.SessionKeepAliveService.startDownload(this, keepAliveTag)
lifecycleScope.launch {
wizardError.value = null
val profile =
Expand Down Expand Up @@ -1408,6 +1429,10 @@ class SetupWizardActivity : FixedFontScaleFragmentActivity() {
refreshAdvancedInstalledSet()
refreshWizardState()
}
com.winlator.cmod.runtime.system.SessionKeepAliveService.stopDownload(
applicationContext,
keepAliveTag,
)
Comment on lines +1432 to +1435
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Release keep-alive tag in finally for advanced installs

stopDownload(...) is called only at the end of the coroutine, so any exception thrown before that point (for example during the post-install refresh calls) skips tag release and leaves activeDownloads populated for that URL. In that state, the keep-alive foreground service can remain active longer than intended, and a later install of the same component will not trigger a fresh start command because HashSet.add returns false. Wrap the whole lifecycleScope.launch body in try/finally so the stop call always runs.

Useful? React with 👍 / 👎.

}
}

Expand Down
Loading