diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6eb46c7219e9d7c2c821cb46ed7869330492165e..07a313bff627d02ba7ae4becb59d3e3bea350dc6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -83,6 +83,15 @@ + + + + + + updateDownloadStatus: ${fusedDownload.name}: $downloadId: $numberOfDownloadedItems/${fusedDownload.downloadIdMap.size}") if (downloadManager.hasDownloadFailed(downloadId)) { handleDownloadFailed(fusedDownload, downloadId) Timber.e( - "Download failed for ${fusedDownload.packageName}, " + "reason: " + "${downloadManager.getDownloadFailureReason(downloadId)}" + "Download failed for ${fusedDownload.packageName}, " + "reason: " + "${ + downloadManager.getDownloadFailureReason( + downloadId + ) + }" ) return@launch } - if (validateDownload(numberOfDownloadedItems, fusedDownload, downloadId)) { - handleDownloadSuccess(fusedDownload) - } + validateDownload(fusedDownload, downloadId) } } } @@ -87,8 +88,10 @@ class DownloadManagerUtils @Inject constructor( private suspend fun handleDownloadSuccess(fusedDownload: FusedDownload) { Timber.i("===> Download is completed for: ${fusedDownload.name}") fusedManagerRepository.moveOBBFileToOBBDirectory(fusedDownload) - fusedDownload.status = Status.DOWNLOADED - fusedManagerRepository.updateFusedDownload(fusedDownload) + if (fusedDownload.status == Status.DOWNLOADING) { + fusedDownload.status = Status.DOWNLOADED + fusedManagerRepository.updateFusedDownload(fusedDownload) + } } private suspend fun handleDownloadFailed(fusedDownload: FusedDownload, downloadId: Long) { @@ -103,20 +106,47 @@ class DownloadManagerUtils @Inject constructor( } private suspend fun validateDownload( - numberOfDownloadedItems: Int, fusedDownload: FusedDownload, downloadId: Long - ): Boolean { + ) { + val incompleteDownloadState = listOf( + PlatformDownloadManager.STATUS_PENDING, + PlatformDownloadManager.STATUS_RUNNING, + PlatformDownloadManager.STATUS_PAUSED, + ) + val isDownloadSuccessful = downloadManager.isDownloadSuccessful(downloadId) + + if (isDownloadSuccessful.first) { + updateDownloadIdMap(fusedDownload, downloadId) + } + + val numberOfDownloadedItems = + fusedDownload.downloadIdMap.values.filter { it }.size + Timber.d("===> updateDownloadStatus: ${fusedDownload.name}: $downloadId: $numberOfDownloadedItems/${fusedDownload.downloadIdMap.size}") + // if download status code is unknown (-1), consider installation is failed. - if (isDownloadSuccessful.second == -1) { - handleDownloadFailed(fusedDownload, downloadId) + val areAllFilesDownloaded = areAllFilesDownloaded( + numberOfDownloadedItems, + fusedDownload + ) + + if (isDownloadSuccessful.first && areAllFilesDownloaded && checkCleanApkSignatureOK(fusedDownload)) { + handleDownloadSuccess(fusedDownload) + return } - return isDownloadSuccessful.first && areAllFilesDownloaded( - numberOfDownloadedItems, fusedDownload - ) && checkCleanApkSignatureOK(fusedDownload) + if (incompleteDownloadState.contains(isDownloadSuccessful.second) + || (isDownloadSuccessful.first && !areAllFilesDownloaded) + ) { + return + } + handleDownloadFailed(fusedDownload, downloadId) + Timber.e( + "Download failed for ${fusedDownload.packageName}: " + + "Download Status: ${isDownloadSuccessful.second}" + ) } private fun areAllFilesDownloaded( diff --git a/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt b/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt index 46dc5a2deac6845da9a0cc93a7443d1d08419651..ea98da5215be4f52735ca3fd50bf3c468c4983c4 100644 --- a/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt +++ b/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt @@ -22,6 +22,7 @@ import android.content.Context import com.aurora.gplayapi.exceptions.ApiException import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R +import foundation.e.apps.data.DownloadManager import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.enums.Status @@ -34,6 +35,7 @@ import foundation.e.apps.data.fusedDownload.FusedManagerRepository import foundation.e.apps.data.fusedDownload.models.FusedDownload import foundation.e.apps.data.playstore.utils.GplayHttpRequestException import foundation.e.apps.data.preference.DataStoreManager +import foundation.e.apps.install.download.DownloadManagerUtils import foundation.e.apps.install.notification.StorageNotificationManager import foundation.e.apps.install.updates.UpdatesNotifier import foundation.e.apps.utils.StorageComputer @@ -41,6 +43,7 @@ import foundation.e.apps.utils.eventBus.AppEvent import foundation.e.apps.utils.eventBus.EventBus import foundation.e.apps.utils.getFormattedString import foundation.e.apps.utils.isNetworkAvailable +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.flow.transformWhile import timber.log.Timber import java.text.NumberFormat @@ -53,9 +56,12 @@ class AppInstallProcessor @Inject constructor( private val fusedManagerRepository: FusedManagerRepository, private val applicationRepository: ApplicationRepository, private val dataStoreManager: DataStoreManager, - private val storageNotificationManager: StorageNotificationManager + private val storageNotificationManager: StorageNotificationManager, ) { + @Inject + lateinit var downloadManager: DownloadManagerUtils + private var isItUpdateWork = false companion object { @@ -197,6 +203,7 @@ class AppInstallProcessor @Inject constructor( ) } + @OptIn(DelicateCoroutinesApi::class) suspend fun processInstall( fusedDownloadId: String, isItUpdateWork: Boolean, @@ -211,6 +218,8 @@ class AppInstallProcessor @Inject constructor( fusedDownload?.let { + checkDownloadingState(fusedDownload) + this.isItUpdateWork = isItUpdateWork && fusedManagerRepository.isFusedDownloadInstalled(fusedDownload) @@ -248,6 +257,15 @@ class AppInstallProcessor @Inject constructor( return Result.success(ResultStatus.OK) } + @OptIn(DelicateCoroutinesApi::class) + private fun checkDownloadingState(fusedDownload: FusedDownload) { + if (fusedDownload.status == Status.DOWNLOADING) { + fusedDownload.downloadIdMap.keys.forEach { downloadId -> + downloadManager.updateDownloadStatus(downloadId) + } + } + } + private fun areFilesDownloadedButNotInstalled(fusedDownload: FusedDownload) = fusedDownload.areFilesDownloaded() && (!fusedManagerRepository.isFusedDownloadInstalled( fusedDownload diff --git a/app/src/main/java/foundation/e/apps/receivers/DumpAppInstallStatusReceiver.kt b/app/src/main/java/foundation/e/apps/receivers/DumpAppInstallStatusReceiver.kt new file mode 100644 index 0000000000000000000000000000000000000000..e21cd022b739691ba86fe68f892f7c8eb6aa659c --- /dev/null +++ b/app/src/main/java/foundation/e/apps/receivers/DumpAppInstallStatusReceiver.kt @@ -0,0 +1,93 @@ +/* + * Copyright MURENA SAS 2024 + * Apps Quickly and easily install Android apps onto your device! + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package foundation.e.apps.receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Context.BATTERY_SERVICE +import android.content.Intent +import android.os.BatteryManager +import com.google.gson.Gson +import dagger.hilt.android.AndroidEntryPoint +import foundation.e.apps.data.Constants +import foundation.e.apps.data.DownloadManager +import foundation.e.apps.data.enums.Status +import foundation.e.apps.data.fusedDownload.FusedDownloadRepository +import foundation.e.apps.data.fusedDownload.models.FusedDownload +import foundation.e.apps.install.download.DownloadManagerUtils +import foundation.e.apps.utils.NetworkStatusManager +import foundation.e.apps.utils.StorageComputer +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + + +@AndroidEntryPoint +class DumpAppInstallStatusReceiver : BroadcastReceiver() { + + @Inject + lateinit var fusedDownloadRepository: FusedDownloadRepository + + @Inject + lateinit var downloadManager: DownloadManager + override fun onReceive(context: Context?, intent: Intent?) { + if (intent?.action == null) { + return + } + + MainScope().launch { + val gson = Gson() + val appList = fusedDownloadRepository.getDownloadList() + Timber.tag(Constants.TAG_APP_INSTALL_STATE) + .i("App install status: ${gson.toJson(appList)}") + + logDownloadStatusFromDownloadManager(appList) + logDeviceStatus(context) + } + } + + private fun logDeviceStatus(context: Context?) { + context?.let { + val bm = context.getSystemService(BATTERY_SERVICE) as BatteryManager + val batteryLevel = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) + + Timber.tag(Constants.TAG_APP_INSTALL_STATE) + .i( + "Available Space: ${StorageComputer.calculateAvailableDiskSpace()}" + + "\nInternet: ${ + NetworkStatusManager.init(context).value + }\nBattery level: $batteryLevel" + ) + } + } + + private fun logDownloadStatusFromDownloadManager(appList: List) { + appList.forEach { + if (listOf(Status.DOWNLOADING, Status.DOWNLOADED).contains(it.status)) { + it.downloadIdMap.keys.forEach { downloadId -> + val downloadStatus = downloadManager.isDownloadSuccessful(downloadId) + Timber.tag(Constants.TAG_APP_INSTALL_STATE) + .i("DownloadStatus: ${it.name}: Id: $downloadId: ${downloadStatus.second}") + } + } + } + } +} diff --git a/app/src/main/java/foundation/e/apps/utils/StorageComputer.kt b/app/src/main/java/foundation/e/apps/utils/StorageComputer.kt index f92e855a44aaca7df0e74cfd4b96e5c02a25a69a..e7be20dba4d87ca434c1a64567761670b03669cf 100644 --- a/app/src/main/java/foundation/e/apps/utils/StorageComputer.kt +++ b/app/src/main/java/foundation/e/apps/utils/StorageComputer.kt @@ -31,7 +31,7 @@ object StorageComputer { private fun getRequiredSpace(fusedDownload: FusedDownload) = fusedDownload.appSize + (500 * (1000 * 1000)) - private fun calculateAvailableDiskSpace(): Long { + fun calculateAvailableDiskSpace(): Long { val path = Environment.getDataDirectory().absolutePath val statFs = StatFs(path) return statFs.availableBytes