Skip to content
Closed
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
4 changes: 4 additions & 0 deletions composeApp/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
<data android:scheme="package" />
</intent-filter>
<!-- Self-update detection: MY_PACKAGE_REPLACED has no data scheme -->
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>

<!-- Cancel action fired from the download progress notification (#373) -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,16 @@ class GithubStoreApp : Application() {
val selfPackageName = packageName
val existing = repo.getAppByPackage(selfPackageName)

if (existing != null) return@launch
if (existing != null) {
// After a self-update the old process is killed before
// ACTION_PACKAGE_REPLACED can be delivered to our own
// receiver, so isPendingInstall stays true. Resolve it
// here at the earliest startup opportunity.
if (existing.isPendingInstall) {
resolveSelfPendingInstall(existing, repo)
}
return@launch
}

val packageMonitor = get<PackageMonitor>()
val systemInfo = packageMonitor.getInstalledPackageInfo(selfPackageName)
Expand Down Expand Up @@ -199,6 +208,39 @@ class GithubStoreApp : Application() {
}
}

/**
* Resolves a stale `isPendingInstall` flag for the app's own
* database row. Called on every cold start when the row exists
* and still has the flag set — the typical scenario after a
* successful self-update where the broadcast path missed.
*/
private suspend fun resolveSelfPendingInstall(
existing: InstalledApp,
repo: InstalledAppsRepository,
) {
try {
val packageMonitor = get<PackageMonitor>()
val systemInfo = packageMonitor.getInstalledPackageInfo(packageName)
if (systemInfo != null) {
val latestVersionCode = existing.latestVersionCode ?: 0L
repo.updateApp(
existing.copy(
isPendingInstall = false,
installedVersionName = systemInfo.versionName,
installedVersionCode = systemInfo.versionCode,
isUpdateAvailable = latestVersionCode > systemInfo.versionCode,
),
)
Logger.i { "Resolved self-update pending install: ${systemInfo.versionName} (code=${systemInfo.versionCode})" }
} else {
repo.updatePendingStatus(packageName, false)
Logger.i { "Resolved self-update pending install (no system info)" }
}
} catch (e: Exception) {
Logger.e(e) { "Failed to resolve self-update pending install" }
}
}

companion object {
private const val SELF_REPO_ID = 1101281251L
private const val SELF_SHA256_FINGERPRINT =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,23 @@ class PackageEventReceiver() :
context: Context?,
intent: Intent?,
) {
val packageName = intent?.data?.schemeSpecificPart ?: return
// MY_PACKAGE_REPLACED has no data URI — the target is the
// receiving app itself. Fall back to the context's package name.
val packageName = intent?.data?.schemeSpecificPart
?: if (intent?.action == Intent.ACTION_MY_PACKAGE_REPLACED) {
context?.packageName
} else {
null
}
?: return

Logger.d { "PackageEventReceiver: ${intent.action} for $packageName" }

try {
when (intent.action) {
Intent.ACTION_PACKAGE_ADDED,
Intent.ACTION_PACKAGE_REPLACED,
Intent.ACTION_MY_PACKAGE_REPLACED,
-> {
scope.launch { onPackageInstalled(packageName) }
}
Expand Down Expand Up @@ -357,6 +366,7 @@ class PackageEventReceiver() :
addAction(Intent.ACTION_PACKAGE_ADDED)
addAction(Intent.ACTION_PACKAGE_REPLACED)
addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED)
addAction(Intent.ACTION_MY_PACKAGE_REPLACED)
addDataScheme("package")
}
}
Expand Down