fix: in-app updater downloads but never installs (#116, #99, #75) - #123
Merged
Conversation
PR #95 switched the PackageInstaller session callback from getActivity to getBroadcast against action `com.arvio.tv.INSTALL_COMPLETE`, but no BroadcastReceiver was ever registered for that action. On Android 7+ the PackageInstaller session API delivers its result by firing the supplied PendingIntent with STATUS_PENDING_USER_ACTION and an EXTRA_INTENT containing the system install-confirmation Activity the app must startActivity() to actually show the "Install?" screen. With no receiver, the session commit succeeded silently, the callback went nowhere, and the user saw "Installing update..." followed by nothing. The APK sat in cache and the old process kept running. This has been broken across multiple versions (1.9.3 through 1.9.73) and directly blocks users from receiving any other fixes we ship. Changes: - New `ApkInstallReceiver` that handles STATUS_PENDING_USER_ACTION by launching the system confirm Activity with NEW_TASK + CLEAR_TOP + GRANT URI permission, and surfaces failure statuses as user-visible toasts instead of silently dropping them. - Register the receiver in AndroidManifest.xml with an intent filter using `${applicationId}.INSTALL_COMPLETE` so the action name is unique per build flavor (play / sideload / staging) and can't collide with other ARVIO installs on the same device. - Derive the broadcast action in `ApkInstaller` from `context.packageName` at runtime via `ApkInstallReceiver.actionFor()`, replacing the hard-coded `com.arvio.tv.INSTALL_COMPLETE` string that was wrong for the `.staging` build flavor. - Use `context.applicationContext` when constructing the PendingIntent and only pass FLAG_MUTABLE on API 31+ (it has no effect on older APIs but keeps the lint clean). - Wrap the ACTION_VIEW fallback path in a try/catch so it no longer crashes on Chinese Android TV forks whose non-AOSP installer rejects the standard Intent. Closes #116 Closes #99 Closes #75
This was referenced Apr 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the long-standing in-app updater bug where the APK downloads successfully but the install screen never appears. Reported multiple times across versions 1.9.3 - 1.9.73:
Root cause
PR #95 switched the
PackageInstallersession callback fromgetActivitytogetBroadcastagainst the actioncom.arvio.tv.INSTALL_COMPLETE, but noBroadcastReceiverwas ever registered for that action anywhere in the project (verified by grep — the only reference was theIntent(...)construction inApkInstaller.kt:123).On Android 7+ the
PackageInstallersession API requires user confirmation for non-privileged apps. It delivers the result by firing the suppliedPendingIntentwith:EXTRA_STATUS==STATUS_PENDING_USER_ACTION, andEXTRA_INTENTcontaining the system install-confirmationActivitythe app muststartActivity()to actually show the "Install?" screen.With no receiver, the session commit succeeded silently, the callback went nowhere, and users saw the "Installing update..." toast followed by nothing. The APK sat in cache and the old process kept running.
Changes
New
ApkInstallReceiver(app/src/main/kotlin/com/arflix/tv/updater/ApkInstallReceiver.kt) that handles:STATUS_PENDING_USER_ACTION→ extractsEXTRA_INTENT, addsFLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP | FLAG_GRANT_READ_URI_PERMISSION, andstartActivity()s the confirm screen.STATUS_SUCCESS→ logs (no toast, the new APK is replacing the running process).STATUS_FAILURE_*values → logs and shows a user-friendly toast explaining what went wrong (storage, conflict, incompatible, blocked, aborted, invalid, etc.).startActivityintry/catchso Chinese Android TV forks whose non-AOSP installer doesn't handle the standard confirm intent degrade gracefully with a user message instead of crashing.Register the receiver in
AndroidManifest.xmlwithandroid:exported="false"and an intent filter using${applicationId}.INSTALL_COMPLETE. The${applicationId}template variable means the action is unique per build flavor (play,sideload,.staging), so the receiver can't collide with other ARVIO installs on the same device.Fix
ApkInstaller.launchInstall:context.packageNameat runtime viaApkInstallReceiver.actionFor(context), replacing the hard-codedcom.arvio.tv.INSTALL_COMPLETEstring that was wrong for the.stagingbuild flavor.context.applicationContextwhen constructing thePendingIntentso it outlives the calling Activity.PendingIntent.FLAG_MUTABLEon API 31+ (has no effect on older APIs but avoids the lint warning and documents intent).ACTION_VIEWfallback path intry/catchso it no longer crashes on forks that reject theapplication/vnd.android.package-archivemime type.Testing
ApkInstallReceiver.onReceive, which can be driven by constructingIntentfixtures with each status value.Risk
Low. The session-based path was already the default; this PR only fixes its broken callback handling. The fallback
ACTION_VIEWpath is unchanged except for defensivetry/catchwrapping. No behavior change for users on Android <7 (wherePackageInstallersession path is already skipped).The action rename from
com.arvio.tv.INSTALL_COMPLETE→${applicationId}.INSTALL_COMPLETEis safe because the only sender (ApkInstaller.launchInstall) and the only receiver (ApkInstallReceiver) are both updated in lockstep in this PR.