From 59d53a8c4c7a709fe96a5d9c39855dd4b7a1b437 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Tue, 10 Mar 2026 00:17:19 +0600 Subject: [PATCH] Fix pre-enqueue install failures not updating button state Persist INSTALLATION_ISSUE when download URL resolution fails (including IllegalStateException) so observers can switch the install button from Cancel to Retry. Limit UpdateEvent emission to update flows only, consolidate URL exception handling, and return early in InstallAppWorker when fused download id is missing. --- .../workmanager/AppInstallProcessor.kt | 126 +++++++++--------- .../install/workmanager/InstallAppWorker.kt | 17 ++- 2 files changed, 76 insertions(+), 67 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallProcessor.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallProcessor.kt index c00981598..267e9733f 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallProcessor.kt +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallProcessor.kt @@ -143,7 +143,10 @@ class AppInstallProcessor @Inject constructor( } } - if (!canEnqueue(appInstall)) return false + if (!canEnqueue(appInstall, isAnUpdate)) { + Timber.w("Can't enqueue ${appInstall.name}/${appInstall.packageName} for installation.") + return false + } appInstallComponents.appManagerWrapper.updateAwaiting(appInstall) @@ -161,8 +164,8 @@ class AppInstallProcessor @Inject constructor( } @VisibleForTesting - suspend fun canEnqueue(appInstall: AppInstall): Boolean { - if (appInstall.type != Type.PWA && !updateDownloadUrls(appInstall)) { + suspend fun canEnqueue(appInstall: AppInstall, isAnUpdate: Boolean = false): Boolean { + if (appInstall.type != Type.PWA && !updateDownloadUrls(appInstall, isAnUpdate)) { return false } @@ -224,34 +227,30 @@ class AppInstallProcessor @Inject constructor( } // returns TRUE if updating urls is successful, otherwise false. - private suspend fun updateDownloadUrls(appInstall: AppInstall): Boolean { + private suspend fun updateDownloadUrls(appInstall: AppInstall, isAnUpdate: Boolean): Boolean { try { updateFusedDownloadWithAppDownloadLink(appInstall) - } catch (e: InternalException.AppNotPurchased) { + } catch (_: InternalException.AppNotPurchased) { if (appInstall.isFree) { handleAppRestricted(appInstall) return false } appInstallComponents.appManagerWrapper.addFusedDownloadPurchaseNeeded(appInstall) EventBus.invokeEvent(AppEvent.AppPurchaseEvent(appInstall)) + return false - } catch (e: GplayHttpRequestException) { - handleUpdateDownloadError( - appInstall, - "${appInstall.packageName} code: ${e.status} exception: ${e.localizedMessage}", - e - ) - return false - } catch (e: IllegalStateException) { - Timber.e(e) } catch (e: Exception) { - handleUpdateDownloadError( - appInstall, - "${appInstall.packageName} exception: ${e.localizedMessage}", - e - ) + val message = if (e is GplayHttpRequestException) { + "${appInstall.packageName} code: ${e.status} exception: ${e.message}" + } else { + "${appInstall.packageName} exception: ${e.message}" + } + Timber.e(e, "Updating download URLS failed for $message") + handleUpdateDownloadError(appInstall, isAnUpdate) + return false } + return true } @@ -261,20 +260,22 @@ class AppInstallProcessor @Inject constructor( appManager.updateUnavailable(appInstall) } - private suspend fun handleUpdateDownloadError( - appInstall: AppInstall, - message: String, - e: Exception - ) { - Timber.e(e, "Updating download Urls failed for $message") - EventBus.invokeEvent( - AppEvent.UpdateEvent( - ResultSupreme.WorkError( - ResultStatus.UNKNOWN, - appInstall + private suspend fun handleUpdateDownloadError(appInstall: AppInstall, isAnUpdate: Boolean) { + if (appInstallComponents.appInstallRepository.getDownloadById(appInstall.id) == null) { + appInstallComponents.appInstallRepository.addDownload(appInstall) + } + appInstallComponents.appManagerWrapper.installationIssue(appInstall) + + if (isAnUpdate) { + EventBus.invokeEvent( + AppEvent.UpdateEvent( + ResultSupreme.WorkError( + ResultStatus.UNKNOWN, + appInstall + ) ) ) - ) + } } private suspend fun updateFusedDownloadWithAppDownloadLink( @@ -294,53 +295,56 @@ class AppInstallProcessor @Inject constructor( ): Result { var appInstall: AppInstall? = null try { - Timber.d("Fused download name $fusedDownloadId") - appInstall = appInstallComponents.appInstallRepository.getDownloadById(fusedDownloadId) - Timber.i(">>> dowork started for Fused download name " + appInstall?.name + " " + fusedDownloadId) - - appInstall?.let { - checkDownloadingState(appInstall) + if (appInstall == null) { + return Result.failure(IllegalStateException("App can't be null here.")) + } - this.isItUpdateWork = - isItUpdateWork && appInstallComponents.appManagerWrapper.isFusedDownloadInstalled( - appInstall - ) + Timber.i(">>> doWork() started for ${appInstall.name}/${appInstall.packageName}") - if (!appInstall.isAppInstalling()) { - Timber.d("!!! returned") - return@let - } + checkDownloadingState(appInstall) - if (!appInstallComponents.appManagerWrapper.validateFusedDownload(appInstall)) { - appInstallComponents.appManagerWrapper.installationIssue(it) - Timber.d("!!! installationIssue") - return@let - } + this.isItUpdateWork = + isItUpdateWork && appInstallComponents.appManagerWrapper.isFusedDownloadInstalled( + appInstall + ) - if (areFilesDownloadedButNotInstalled(appInstall)) { - Timber.i("===> Downloaded But not installed ${appInstall.name}") - appInstallComponents.appManagerWrapper.updateDownloadStatus( - appInstall, - Status.INSTALLING - ) - } + if (!appInstall.isAppInstalling()) { + val message = "${appInstall.status} is in invalid state" + Timber.w(message) + return Result.failure(IllegalStateException(message)) + } - runInForeground.invoke(it.name) + if (!appInstallComponents.appManagerWrapper.validateFusedDownload(appInstall)) { + appInstallComponents.appManagerWrapper.installationIssue(appInstall) + val message = "Installation issue for ${appInstall.name}/${appInstall.packageName}" + Timber.w(message) + return Result.failure(IllegalStateException(message)) + } - startAppInstallationProcess(it) + if (areFilesDownloadedButNotInstalled(appInstall)) { + Timber.i("===> Downloaded But not installed ${appInstall.name}") + appInstallComponents.appManagerWrapper.updateDownloadStatus( + appInstall, + Status.INSTALLING + ) } + + runInForeground.invoke(appInstall.name) + + startAppInstallationProcess(appInstall) } catch (e: Exception) { Timber.e( e, - "Install worker is failed for ${appInstall?.packageName} exception: ${e.localizedMessage}" + "Install worker failed for ${appInstall?.packageName} exception: ${e.message}" ) appInstall?.let { appInstallComponents.appManagerWrapper.cancelDownload(appInstall) } + return Result.failure(e) } - Timber.i("doWork: RESULT SUCCESS: ${appInstall?.name}") + return Result.success(ResultStatus.OK) } diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallAppWorker.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallAppWorker.kt index c4af7cfbb..6ea50e733 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallAppWorker.kt +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallAppWorker.kt @@ -59,15 +59,20 @@ class InstallAppWorker @AssistedInject constructor( override suspend fun doWork(): Result { val fusedDownloadId = params.inputData.getString(INPUT_DATA_FUSED_DOWNLOAD) ?: "" + if (fusedDownloadId.isEmpty()) { + return Result.failure() + } + val isPackageUpdate = params.inputData.getBoolean(IS_UPDATE_WORK, false) val response = appInstallProcessor.processInstall(fusedDownloadId, isPackageUpdate) { title -> - setForeground( - createForegroundInfo( - "${context.getString(R.string.installing)} $title" - ) - ) + setForeground(createForegroundInfo("${context.getString(R.string.installing)} $title")) + } + + return if (response.isSuccess) { + Result.success() + } else { + Result.failure() } - return if (response.isSuccess) Result.success() else Result.failure() } private fun createForegroundInfo(progress: String): ForegroundInfo { -- GitLab