diff --git a/app/src/main/java/foundation/e/apps/manager/database/DatabaseRepository.kt b/app/src/main/java/foundation/e/apps/manager/database/DatabaseRepository.kt index 5124623e6af75a198c13fbc1273cd24fb6a3ed9c..bc2614918db4b15b86e1f4176de19f7f7cc65a7c 100644 --- a/app/src/main/java/foundation/e/apps/manager/database/DatabaseRepository.kt +++ b/app/src/main/java/foundation/e/apps/manager/database/DatabaseRepository.kt @@ -53,7 +53,7 @@ class DatabaseRepository @Inject constructor( } } - fun getDownloadFlowById(id: String): Flow { + fun getDownloadFlowById(id: String): Flow { return fusedDownloadDAO.getDownloadFlowById(id).asFlow() } } diff --git a/app/src/main/java/foundation/e/apps/manager/download/DownloadManagerBR.kt b/app/src/main/java/foundation/e/apps/manager/download/DownloadManagerBR.kt index cd96d877280fe60422b990cacf0c08bf1d792dad..08c575860246757fcb85e36994f89717811c3cf1 100644 --- a/app/src/main/java/foundation/e/apps/manager/download/DownloadManagerBR.kt +++ b/app/src/main/java/foundation/e/apps/manager/download/DownloadManagerBR.kt @@ -22,6 +22,7 @@ import android.app.DownloadManager import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import androidx.annotation.UiThread import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.DelicateCoroutinesApi import timber.log.Timber @@ -36,9 +37,9 @@ class DownloadManagerBR : BroadcastReceiver() { companion object { private const val TAG = "DownloadManagerBR" - val downloadedList = mutableListOf() } + @UiThread override fun onReceive(context: Context?, intent: Intent?) { val action = intent?.action if (context != null && action != null) { @@ -46,7 +47,6 @@ class DownloadManagerBR : BroadcastReceiver() { Timber.i("onReceive: DownloadBR $action $id") when (action) { DownloadManager.ACTION_DOWNLOAD_COMPLETE -> { - downloadedList.add(id) downloadManagerUtils.updateDownloadStatus(id) } DownloadManager.ACTION_NOTIFICATION_CLICKED -> { diff --git a/app/src/main/java/foundation/e/apps/manager/fused/FusedManagerImpl.kt b/app/src/main/java/foundation/e/apps/manager/fused/FusedManagerImpl.kt index def2d5e64c474a53c8462c8f31459c628cde8736..d4899911d3eee43e9343f90fecffe5cbf17b2d00 100644 --- a/app/src/main/java/foundation/e/apps/manager/fused/FusedManagerImpl.kt +++ b/app/src/main/java/foundation/e/apps/manager/fused/FusedManagerImpl.kt @@ -33,6 +33,7 @@ import foundation.e.apps.manager.database.fusedDownload.FusedDownload import foundation.e.apps.manager.download.DownloadManagerBR import foundation.e.apps.manager.download.data.DownloadProgressLD import foundation.e.apps.manager.pkg.PkgManagerModule +import foundation.e.apps.manager.workmanager.InstallWorkManager import foundation.e.apps.utils.enums.Status import foundation.e.apps.utils.enums.Type import foundation.e.apps.utils.modules.PWAManagerModule @@ -61,6 +62,8 @@ class FusedManagerImpl @Inject constructor( private val TAG = FusedManagerImpl::class.java.simpleName + private val mutex = Mutex() + @RequiresApi(Build.VERSION_CODES.O) override fun createNotificationChannels() { notificationManager.apply { @@ -95,7 +98,6 @@ class FusedManagerImpl @Inject constructor( override suspend fun updateDownloadStatus(fusedDownload: FusedDownload, status: Status) { if (status == Status.INSTALLED) { fusedDownload.status = status - DownloadManagerBR.downloadedList.clear() flushOldDownload(fusedDownload.packageName) databaseRepository.deleteDownload(fusedDownload) } else if (status == Status.INSTALLING) { @@ -106,8 +108,6 @@ class FusedManagerImpl @Inject constructor( } } - private val mutex = Mutex() - override suspend fun downloadApp(fusedDownload: FusedDownload) { mutex.withLock { when (fusedDownload.type) { @@ -147,23 +147,26 @@ class FusedManagerImpl @Inject constructor( @OptIn(DelicateCoroutinesApi::class) override suspend fun cancelDownload(fusedDownload: FusedDownload) { - if (fusedDownload.id.isNotBlank()) { - fusedDownload.downloadIdMap.forEach { (key, _) -> - downloadManager.remove(key) + mutex.withLock { + if (fusedDownload.id.isNotBlank()) { + removeFusedDownload(fusedDownload) + } else { + Timber.d("Unable to cancel download!") } - DownloadProgressLD.setDownloadId(-1) - DownloadManagerBR.downloadedList.clear() + } + } - // Reset the status before deleting download - updateDownloadStatus(fusedDownload, fusedDownload.orgStatus) - if (fusedDownload.status != Status.INSTALLATION_ISSUE) { - databaseRepository.deleteDownload(fusedDownload) - } + private suspend fun removeFusedDownload(fusedDownload: FusedDownload) { + fusedDownload.downloadIdMap.forEach { (key, _) -> + downloadManager.remove(key) + } + DownloadProgressLD.setDownloadId(-1) - flushOldDownload(fusedDownload.packageName) - } else { - Timber.d("Unable to cancel download!") + if (fusedDownload.status != Status.INSTALLATION_ISSUE) { + databaseRepository.deleteDownload(fusedDownload) } + + flushOldDownload(fusedDownload.packageName) } override suspend fun getFusedDownload(downloadId: Long, packageName: String): FusedDownload { diff --git a/app/src/main/java/foundation/e/apps/manager/fused/FusedManagerRepository.kt b/app/src/main/java/foundation/e/apps/manager/fused/FusedManagerRepository.kt index e59041073a5b38fd081357b0ce3e99e1dba30bca..9a433e9712a5c97e98e1b9df20422dbc7a798c53 100644 --- a/app/src/main/java/foundation/e/apps/manager/fused/FusedManagerRepository.kt +++ b/app/src/main/java/foundation/e/apps/manager/fused/FusedManagerRepository.kt @@ -37,11 +37,11 @@ class FusedManagerRepository @Inject constructor( } suspend fun addDownload(fusedDownload: FusedDownload): Boolean { - if (InstallWorkManager.checkWorkIsAlreadyAvailable(fusedDownload.id)) { + val existingFusedDownload = fusedManagerImpl.getDownloadById(fusedDownload) + if (isInstallWorkRunning(existingFusedDownload, fusedDownload)) { return false } - val existingFusedDownload = fusedManagerImpl.getDownloadById(fusedDownload) // We don't want to add any thing, if it already exists without INSTALLATION_ISSUE if (existingFusedDownload != null && existingFusedDownload.status != Status.INSTALLATION_ISSUE) { return false @@ -51,6 +51,14 @@ class FusedManagerRepository @Inject constructor( return true } + private fun isInstallWorkRunning( + existingFusedDownload: FusedDownload?, + fusedDownload: FusedDownload + ) = + existingFusedDownload != null && InstallWorkManager.checkWorkIsAlreadyAvailable( + fusedDownload.id + ) + suspend fun addFusedDownloadPurchaseNeeded(fusedDownload: FusedDownload) { fusedManagerImpl.insertFusedDownloadPurchaseNeeded(fusedDownload) } diff --git a/app/src/main/java/foundation/e/apps/manager/workmanager/AppInstallProcessor.kt b/app/src/main/java/foundation/e/apps/manager/workmanager/AppInstallProcessor.kt index 1ae34fa78a1555b4de9f79af14e57d07bebbaa9f..5ff19decb3063e37ee930386179c27a71af5f581 100644 --- a/app/src/main/java/foundation/e/apps/manager/workmanager/AppInstallProcessor.kt +++ b/app/src/main/java/foundation/e/apps/manager/workmanager/AppInstallProcessor.kt @@ -29,20 +29,12 @@ import foundation.e.apps.updates.UpdatesNotifier import foundation.e.apps.utils.enums.ResultStatus import foundation.e.apps.utils.enums.Status import foundation.e.apps.utils.modules.DataStoreManager -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.flow.transformWhile import timber.log.Timber import java.text.NumberFormat import java.text.SimpleDateFormat import java.util.Date import javax.inject.Inject -import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds class AppInstallProcessor @Inject constructor( @ApplicationContext private val context: Context, @@ -51,15 +43,12 @@ class AppInstallProcessor @Inject constructor( private val dataStoreManager: DataStoreManager ) { - private var isDownloading: Boolean = false private var isItUpdateWork = false companion object { private const val TAG = "AppInstallProcessor" } - private val mutex = Mutex(true) - suspend fun processInstall( fusedDownloadId: String, isItUpdateWork: Boolean, @@ -75,7 +64,7 @@ class AppInstallProcessor @Inject constructor( fusedDownload?.let { this.isItUpdateWork = isItUpdateWork && - fusedManagerRepository.isFusedDownloadInstalled(fusedDownload) + fusedManagerRepository.isFusedDownloadInstalled(fusedDownload) if (!fusedDownload.isAppInstalling()) { Timber.d("!!! returned") @@ -96,7 +85,6 @@ class AppInstallProcessor @Inject constructor( runInForeground?.invoke(it.name) startAppInstallationProcess(it) - mutex.lock() } } catch (e: Exception) { Timber.e("doWork: Failed: ${e.stackTraceToString()}") @@ -111,17 +99,18 @@ class AppInstallProcessor @Inject constructor( private fun areFilesDownloadedButNotInstalled(fusedDownload: FusedDownload) = fusedDownload.areFilesDownloaded() && ( - !fusedManagerRepository.isFusedDownloadInstalled( - fusedDownload - ) || fusedDownload.status == Status.INSTALLING - ) + !fusedManagerRepository.isFusedDownloadInstalled( + fusedDownload + ) || fusedDownload.status == Status.INSTALLING + ) private suspend fun checkUpdateWork( fusedDownload: FusedDownload? ) { if (isItUpdateWork) { fusedDownload?.let { - val packageStatus = fusedManagerRepository.getFusedDownloadPackageStatus(fusedDownload) + val packageStatus = + fusedManagerRepository.getFusedDownloadPackageStatus(fusedDownload) if (packageStatus == Status.INSTALLED) { UpdatesDao.addSuccessfullyUpdatedApp(it) @@ -165,33 +154,44 @@ class AppInstallProcessor @Inject constructor( ) } - private suspend fun startAppInstallationProcess( - fusedDownload: FusedDownload - ): Boolean { + private suspend fun startAppInstallationProcess(fusedDownload: FusedDownload) { if (fusedDownload.isAwaiting()) { fusedManagerRepository.downloadApp(fusedDownload) Timber.i("===> doWork: Download started ${fusedDownload.name} ${fusedDownload.status}") } - isDownloading = true - /** - * observe app download/install process in a separate thread as DownloadManager download artifacts in a separate process - * It checks install status every three seconds - */ - tickerFlow(3.seconds) - .onEach { - val download = databaseRepository.getDownloadById(fusedDownload.id) - if (download == null) { - Timber.d("===> download null: finish installation") - finishInstallation(fusedDownload) - } else { - handleFusedDownloadStatusCheckingException(download) - } - }.launchIn(CoroutineScope(Dispatchers.IO)) - Timber.d(">>> ===> doWork: Download started " + fusedDownload.name + " " + fusedDownload.status) - return true + databaseRepository.getDownloadFlowById(fusedDownload.id) + .transformWhile { + emit(it) + isInstallRunning(it) + }.collect { latestFusedDownload -> + handleFusedDownload(latestFusedDownload, fusedDownload) + } } + /** + * Takes actions depending on the status of [FusedDownload] + * + * @param latestFusedDownload comes from Room database when [Status] is updated + * @param fusedDownload is the original object when install process isn't started. It's used when [latestFusedDownload] + * becomes null, After installation is completed. + */ + private suspend fun handleFusedDownload( + latestFusedDownload: FusedDownload?, + fusedDownload: FusedDownload + ) { + if (latestFusedDownload == null) { + Timber.d("===> download null: finish installation") + finishInstallation(fusedDownload) + return + } + + handleFusedDownloadStatusCheckingException(latestFusedDownload) + } + + private fun isInstallRunning(it: FusedDownload?) = + it != null && it.status != Status.INSTALLATION_ISSUE + private suspend fun handleFusedDownloadStatusCheckingException( download: FusedDownload ) { @@ -204,19 +204,6 @@ class AppInstallProcessor @Inject constructor( } } - /** - * Triggers a repetitive event according to the delay passed in the parameter - * @param period delay of each event - * @param initialDelay initial delay to trigger the first event - */ - private fun tickerFlow(period: Duration, initialDelay: Duration = Duration.ZERO) = flow { - delay(initialDelay) - while (isDownloading) { - emit(Unit) - delay(period) - } - } - private suspend fun handleFusedDownloadStatus(fusedDownload: FusedDownload) { when (fusedDownload.status) { Status.AWAITING, Status.DOWNLOADING -> { @@ -243,13 +230,5 @@ class AppInstallProcessor @Inject constructor( private suspend fun finishInstallation(fusedDownload: FusedDownload) { checkUpdateWork(fusedDownload) - isDownloading = false - unlockMutex() - } - - private fun unlockMutex() { - if (mutex.isLocked) { - mutex.unlock() - } } }