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