Loading app/src/main/java/foundation/e/apps/data/di/bindings/AppInstallationModule.kt +20 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,9 @@ import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.core.helper.InstallationCompletionHandler import foundation.e.apps.data.install.download.DownloadManagerUtils import foundation.e.apps.data.install.wrapper.AppEventDispatcher import foundation.e.apps.data.install.wrapper.DefaultAppEventDispatcher import foundation.e.apps.data.install.wrapper.DeviceNetworkStatusChecker Loading @@ -34,6 +37,9 @@ import foundation.e.apps.data.install.wrapper.UpdatesNotificationSender import foundation.e.apps.data.install.wrapper.UpdatesNotificationSenderImpl import foundation.e.apps.data.install.wrapper.UpdatesTracker import foundation.e.apps.data.install.wrapper.UpdatesTrackerImpl import foundation.e.apps.data.installation.port.InstallationAppManager import foundation.e.apps.data.installation.port.InstallationCompletionNotifier import foundation.e.apps.data.installation.port.InstallationDownloadStatusUpdater import javax.inject.Singleton @Module Loading Loading @@ -63,4 +69,18 @@ interface AppInstallationModule { @Binds @Singleton fun bindNetworkStatusChecker(checker: DeviceNetworkStatusChecker): NetworkStatusChecker @Binds @Singleton fun bindInstallationAppManager(appManagerWrapper: AppManagerWrapper): InstallationAppManager @Binds @Singleton fun bindInstallationDownloadStatusUpdater( downloadManagerUtils: DownloadManagerUtils ): InstallationDownloadStatusUpdater @Binds @Singleton fun bindInstallationCompletionNotifier(handler: InstallationCompletionHandler): InstallationCompletionNotifier } app/src/main/java/foundation/e/apps/data/install/AppManagerWrapper.kt +12 −6 Original line number Diff line number Diff line Loading @@ -27,25 +27,27 @@ import foundation.e.apps.data.fdroid.FDroidRepository import foundation.e.apps.data.install.download.data.DownloadProgress import foundation.e.apps.data.install.workmanager.InstallWorkManager import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.installation.port.InstallationAppManager import foundation.e.apps.domain.model.install.Status import javax.inject.Inject import javax.inject.Singleton private const val PERCENTAGE_MULTIPLIER = 100 @Suppress("TooManyFunctions") @Singleton @OpenForTesting class AppManagerWrapper @Inject constructor( @ApplicationContext private val context: Context, private val appManager: AppManager, private val fDroidRepository: FDroidRepository ) { ) : InstallationAppManager { fun createNotificationChannels() { return appManager.createNotificationChannels() } suspend fun downloadApp(appInstall: AppInstall, isUpdate: Boolean = false) { override suspend fun downloadApp(appInstall: AppInstall, isUpdate: Boolean) { return appManager.downloadApp(appInstall, isUpdate) } Loading Loading @@ -100,15 +102,19 @@ class AppManagerWrapper @Inject constructor( return appManager.getFusedDownload(downloadId, packageName) } suspend fun updateDownloadStatus(appInstall: AppInstall, status: Status) { override suspend fun updateDownloadStatus(appInstall: AppInstall, status: Status) { return appManager.updateDownloadStatus(appInstall, status) } override suspend fun cancelDownload(appInstall: AppInstall) { return appManager.cancelDownload(appInstall, "") } suspend fun cancelDownload(appInstall: AppInstall, packageName: String = "") { return appManager.cancelDownload(appInstall, packageName) } suspend fun installationIssue(appInstall: AppInstall) { override suspend fun installationIssue(appInstall: AppInstall) { return appManager.reportInstallationIssue(appInstall) } Loading @@ -124,7 +130,7 @@ class AppManagerWrapper @Inject constructor( appManager.updateAppInstall(appInstall) } fun validateFusedDownload(appInstall: AppInstall) = override fun validateFusedDownload(appInstall: AppInstall) = appInstall.packageName.isNotEmpty() && appInstall.downloadURLList.isNotEmpty() suspend fun calculateProgress( Loading Loading @@ -212,7 +218,7 @@ class AppManagerWrapper @Inject constructor( ) } fun isFusedDownloadInstalled(appInstall: AppInstall): Boolean { override fun isFusedDownloadInstalled(appInstall: AppInstall): Boolean { return appManager.isAppInstalled(appInstall) } Loading app/src/main/java/foundation/e/apps/data/install/core/InstallationProcessor.kt +18 −17 Original line number Diff line number Diff line Loading @@ -19,10 +19,10 @@ package foundation.e.apps.data.install.core import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.core.helper.InstallationCompletionHandler import foundation.e.apps.data.install.download.DownloadManagerUtils import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.installation.port.InstallationAppManager import foundation.e.apps.data.installation.port.InstallationCompletionNotifier import foundation.e.apps.data.installation.port.InstallationDownloadStatusUpdater import foundation.e.apps.data.installation.repository.AppInstallRepository import foundation.e.apps.domain.model.install.Status import kotlinx.coroutines.CancellationException Loading @@ -33,9 +33,9 @@ import javax.inject.Inject class InstallationProcessor @Inject constructor( private val appInstallRepository: AppInstallRepository, private val appManagerWrapper: AppManagerWrapper, private val downloadManager: DownloadManagerUtils, private val installationCompletionHandler: InstallationCompletionHandler, private val installationAppManager: InstallationAppManager, private val installationDownloadStatusUpdater: InstallationDownloadStatusUpdater, private val installationCompletionNotifier: InstallationCompletionNotifier, ) { @Suppress("ReturnCount") @OptIn(DelicateCoroutinesApi::class) Loading @@ -60,8 +60,8 @@ class InstallationProcessor @Inject constructor( return Result.failure(IllegalStateException(message)) } if (!appManagerWrapper.validateFusedDownload(appInstall)) { appManagerWrapper.installationIssue(appInstall) if (!installationAppManager.validateFusedDownload(appInstall)) { installationAppManager.installationIssue(appInstall) val message = "Installation issue for ${appInstall.name}/${appInstall.packageName}" Timber.w(message) Loading @@ -70,11 +70,11 @@ class InstallationProcessor @Inject constructor( return runCatching { val isUpdateWork = isItUpdateWork && appManagerWrapper.isFusedDownloadInstalled(appInstall) isItUpdateWork && installationAppManager.isFusedDownloadInstalled(appInstall) if (areFilesDownloadedButNotInstalled(appInstall)) { Timber.i("===> Downloaded But not installed ${appInstall.name}") appManagerWrapper.updateDownloadStatus(appInstall, Status.INSTALLING) installationAppManager.updateDownloadStatus(appInstall, Status.INSTALLING) } runInForeground.invoke(appInstall.name) Loading @@ -92,7 +92,8 @@ class InstallationProcessor @Inject constructor( "Install worker failed for ${appInstall.packageName} exception: ${exception.message}" ) appManagerWrapper.cancelDownload(appInstall) installationAppManager.installationIssue(appInstall) installationAppManager.cancelDownload(appInstall) } } Loading @@ -100,17 +101,17 @@ class InstallationProcessor @Inject constructor( private fun checkDownloadingState(appInstall: AppInstall) { if (appInstall.status == Status.DOWNLOADING) { appInstall.downloadIdMap.keys.forEach { downloadId -> downloadManager.updateDownloadStatus(downloadId) installationDownloadStatusUpdater.updateDownloadStatus(downloadId) } } } private fun areFilesDownloadedButNotInstalled(appInstall: AppInstall): Boolean = appInstall.areFilesDownloaded() && (!appManagerWrapper.isFusedDownloadInstalled(appInstall) || appInstall.status == Status.INSTALLING) (!installationAppManager.isFusedDownloadInstalled(appInstall) || appInstall.status == Status.INSTALLING) private suspend fun startAppInstallationProcess(appInstall: AppInstall, isUpdateWork: Boolean) { if (appInstall.isAwaiting()) { appManagerWrapper.downloadApp(appInstall, isUpdateWork) installationAppManager.downloadApp(appInstall, isUpdateWork) Timber.i("===> doWork: Download started ${appInstall.name} ${appInstall.status}") } Loading Loading @@ -155,7 +156,7 @@ class InstallationProcessor @Inject constructor( "Handling install status is failed for ${download.packageName} " + "exception: ${throwable.localizedMessage}" Timber.e(throwable, message) appManagerWrapper.installationIssue(download) installationAppManager.installationIssue(download) finishInstallation(download, isUpdateWork) } Loading @@ -167,7 +168,7 @@ class InstallationProcessor @Inject constructor( private suspend fun handleFusedDownloadStatus(appInstall: AppInstall, isUpdateWork: Boolean) { when (appInstall.status) { Status.AWAITING, Status.DOWNLOADING -> Unit Status.DOWNLOADED -> appManagerWrapper.updateDownloadStatus( Status.DOWNLOADED -> installationAppManager.updateDownloadStatus( appInstall, Status.INSTALLING ) Loading @@ -186,6 +187,6 @@ class InstallationProcessor @Inject constructor( } private suspend fun finishInstallation(appInstall: AppInstall, isUpdateWork: Boolean) { installationCompletionHandler.onInstallFinished(appInstall, isUpdateWork) installationCompletionNotifier.onInstallFinished(appInstall, isUpdateWork) } } app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationCompletionHandler.kt +3 −2 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.wrapper.UpdatesNotificationSender import foundation.e.apps.data.install.wrapper.UpdatesTracker import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.installation.port.InstallationCompletionNotifier import foundation.e.apps.data.installation.repository.AppInstallRepository import foundation.e.apps.data.preference.PlayStoreAuthStore import foundation.e.apps.data.utils.getFormattedString Loading @@ -40,12 +41,12 @@ class InstallationCompletionHandler @Inject constructor( private val playStoreAuthStore: PlayStoreAuthStore, private val updatesTracker: UpdatesTracker, private val updatesNotificationSender: UpdatesNotificationSender, ) { ) : InstallationCompletionNotifier { companion object { private const val DATE_FORMAT = "dd/MM/yyyy-HH:mm" } suspend fun onInstallFinished(appInstall: AppInstall?, isUpdateWork: Boolean) { override suspend fun onInstallFinished(appInstall: AppInstall?, isUpdateWork: Boolean) { if (!isUpdateWork) { return } Loading app/src/main/java/foundation/e/apps/data/install/download/DownloadManagerUtils.kt +3 −2 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.notification.StorageNotificationManager import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.installation.model.InstallationSource import foundation.e.apps.data.installation.port.InstallationDownloadStatusUpdater import foundation.e.apps.domain.model.install.Status import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi Loading @@ -48,7 +49,7 @@ class DownloadManagerUtils @Inject constructor( private val downloadManager: DownloadManager, private val storageNotificationManager: StorageNotificationManager, @IoCoroutineScope private val coroutineScope: CoroutineScope ) { ) : InstallationDownloadStatusUpdater { private val mutex = Mutex() @DelicateCoroutinesApi Loading @@ -60,7 +61,7 @@ class DownloadManagerUtils @Inject constructor( } @DelicateCoroutinesApi fun updateDownloadStatus(downloadId: Long) { override fun updateDownloadStatus(downloadId: Long) { coroutineScope.launch { mutex.withLock { // Waiting for DownloadManager to publish the progress of last bytes Loading Loading
app/src/main/java/foundation/e/apps/data/di/bindings/AppInstallationModule.kt +20 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,9 @@ import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.core.helper.InstallationCompletionHandler import foundation.e.apps.data.install.download.DownloadManagerUtils import foundation.e.apps.data.install.wrapper.AppEventDispatcher import foundation.e.apps.data.install.wrapper.DefaultAppEventDispatcher import foundation.e.apps.data.install.wrapper.DeviceNetworkStatusChecker Loading @@ -34,6 +37,9 @@ import foundation.e.apps.data.install.wrapper.UpdatesNotificationSender import foundation.e.apps.data.install.wrapper.UpdatesNotificationSenderImpl import foundation.e.apps.data.install.wrapper.UpdatesTracker import foundation.e.apps.data.install.wrapper.UpdatesTrackerImpl import foundation.e.apps.data.installation.port.InstallationAppManager import foundation.e.apps.data.installation.port.InstallationCompletionNotifier import foundation.e.apps.data.installation.port.InstallationDownloadStatusUpdater import javax.inject.Singleton @Module Loading Loading @@ -63,4 +69,18 @@ interface AppInstallationModule { @Binds @Singleton fun bindNetworkStatusChecker(checker: DeviceNetworkStatusChecker): NetworkStatusChecker @Binds @Singleton fun bindInstallationAppManager(appManagerWrapper: AppManagerWrapper): InstallationAppManager @Binds @Singleton fun bindInstallationDownloadStatusUpdater( downloadManagerUtils: DownloadManagerUtils ): InstallationDownloadStatusUpdater @Binds @Singleton fun bindInstallationCompletionNotifier(handler: InstallationCompletionHandler): InstallationCompletionNotifier }
app/src/main/java/foundation/e/apps/data/install/AppManagerWrapper.kt +12 −6 Original line number Diff line number Diff line Loading @@ -27,25 +27,27 @@ import foundation.e.apps.data.fdroid.FDroidRepository import foundation.e.apps.data.install.download.data.DownloadProgress import foundation.e.apps.data.install.workmanager.InstallWorkManager import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.installation.port.InstallationAppManager import foundation.e.apps.domain.model.install.Status import javax.inject.Inject import javax.inject.Singleton private const val PERCENTAGE_MULTIPLIER = 100 @Suppress("TooManyFunctions") @Singleton @OpenForTesting class AppManagerWrapper @Inject constructor( @ApplicationContext private val context: Context, private val appManager: AppManager, private val fDroidRepository: FDroidRepository ) { ) : InstallationAppManager { fun createNotificationChannels() { return appManager.createNotificationChannels() } suspend fun downloadApp(appInstall: AppInstall, isUpdate: Boolean = false) { override suspend fun downloadApp(appInstall: AppInstall, isUpdate: Boolean) { return appManager.downloadApp(appInstall, isUpdate) } Loading Loading @@ -100,15 +102,19 @@ class AppManagerWrapper @Inject constructor( return appManager.getFusedDownload(downloadId, packageName) } suspend fun updateDownloadStatus(appInstall: AppInstall, status: Status) { override suspend fun updateDownloadStatus(appInstall: AppInstall, status: Status) { return appManager.updateDownloadStatus(appInstall, status) } override suspend fun cancelDownload(appInstall: AppInstall) { return appManager.cancelDownload(appInstall, "") } suspend fun cancelDownload(appInstall: AppInstall, packageName: String = "") { return appManager.cancelDownload(appInstall, packageName) } suspend fun installationIssue(appInstall: AppInstall) { override suspend fun installationIssue(appInstall: AppInstall) { return appManager.reportInstallationIssue(appInstall) } Loading @@ -124,7 +130,7 @@ class AppManagerWrapper @Inject constructor( appManager.updateAppInstall(appInstall) } fun validateFusedDownload(appInstall: AppInstall) = override fun validateFusedDownload(appInstall: AppInstall) = appInstall.packageName.isNotEmpty() && appInstall.downloadURLList.isNotEmpty() suspend fun calculateProgress( Loading Loading @@ -212,7 +218,7 @@ class AppManagerWrapper @Inject constructor( ) } fun isFusedDownloadInstalled(appInstall: AppInstall): Boolean { override fun isFusedDownloadInstalled(appInstall: AppInstall): Boolean { return appManager.isAppInstalled(appInstall) } Loading
app/src/main/java/foundation/e/apps/data/install/core/InstallationProcessor.kt +18 −17 Original line number Diff line number Diff line Loading @@ -19,10 +19,10 @@ package foundation.e.apps.data.install.core import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.core.helper.InstallationCompletionHandler import foundation.e.apps.data.install.download.DownloadManagerUtils import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.installation.port.InstallationAppManager import foundation.e.apps.data.installation.port.InstallationCompletionNotifier import foundation.e.apps.data.installation.port.InstallationDownloadStatusUpdater import foundation.e.apps.data.installation.repository.AppInstallRepository import foundation.e.apps.domain.model.install.Status import kotlinx.coroutines.CancellationException Loading @@ -33,9 +33,9 @@ import javax.inject.Inject class InstallationProcessor @Inject constructor( private val appInstallRepository: AppInstallRepository, private val appManagerWrapper: AppManagerWrapper, private val downloadManager: DownloadManagerUtils, private val installationCompletionHandler: InstallationCompletionHandler, private val installationAppManager: InstallationAppManager, private val installationDownloadStatusUpdater: InstallationDownloadStatusUpdater, private val installationCompletionNotifier: InstallationCompletionNotifier, ) { @Suppress("ReturnCount") @OptIn(DelicateCoroutinesApi::class) Loading @@ -60,8 +60,8 @@ class InstallationProcessor @Inject constructor( return Result.failure(IllegalStateException(message)) } if (!appManagerWrapper.validateFusedDownload(appInstall)) { appManagerWrapper.installationIssue(appInstall) if (!installationAppManager.validateFusedDownload(appInstall)) { installationAppManager.installationIssue(appInstall) val message = "Installation issue for ${appInstall.name}/${appInstall.packageName}" Timber.w(message) Loading @@ -70,11 +70,11 @@ class InstallationProcessor @Inject constructor( return runCatching { val isUpdateWork = isItUpdateWork && appManagerWrapper.isFusedDownloadInstalled(appInstall) isItUpdateWork && installationAppManager.isFusedDownloadInstalled(appInstall) if (areFilesDownloadedButNotInstalled(appInstall)) { Timber.i("===> Downloaded But not installed ${appInstall.name}") appManagerWrapper.updateDownloadStatus(appInstall, Status.INSTALLING) installationAppManager.updateDownloadStatus(appInstall, Status.INSTALLING) } runInForeground.invoke(appInstall.name) Loading @@ -92,7 +92,8 @@ class InstallationProcessor @Inject constructor( "Install worker failed for ${appInstall.packageName} exception: ${exception.message}" ) appManagerWrapper.cancelDownload(appInstall) installationAppManager.installationIssue(appInstall) installationAppManager.cancelDownload(appInstall) } } Loading @@ -100,17 +101,17 @@ class InstallationProcessor @Inject constructor( private fun checkDownloadingState(appInstall: AppInstall) { if (appInstall.status == Status.DOWNLOADING) { appInstall.downloadIdMap.keys.forEach { downloadId -> downloadManager.updateDownloadStatus(downloadId) installationDownloadStatusUpdater.updateDownloadStatus(downloadId) } } } private fun areFilesDownloadedButNotInstalled(appInstall: AppInstall): Boolean = appInstall.areFilesDownloaded() && (!appManagerWrapper.isFusedDownloadInstalled(appInstall) || appInstall.status == Status.INSTALLING) (!installationAppManager.isFusedDownloadInstalled(appInstall) || appInstall.status == Status.INSTALLING) private suspend fun startAppInstallationProcess(appInstall: AppInstall, isUpdateWork: Boolean) { if (appInstall.isAwaiting()) { appManagerWrapper.downloadApp(appInstall, isUpdateWork) installationAppManager.downloadApp(appInstall, isUpdateWork) Timber.i("===> doWork: Download started ${appInstall.name} ${appInstall.status}") } Loading Loading @@ -155,7 +156,7 @@ class InstallationProcessor @Inject constructor( "Handling install status is failed for ${download.packageName} " + "exception: ${throwable.localizedMessage}" Timber.e(throwable, message) appManagerWrapper.installationIssue(download) installationAppManager.installationIssue(download) finishInstallation(download, isUpdateWork) } Loading @@ -167,7 +168,7 @@ class InstallationProcessor @Inject constructor( private suspend fun handleFusedDownloadStatus(appInstall: AppInstall, isUpdateWork: Boolean) { when (appInstall.status) { Status.AWAITING, Status.DOWNLOADING -> Unit Status.DOWNLOADED -> appManagerWrapper.updateDownloadStatus( Status.DOWNLOADED -> installationAppManager.updateDownloadStatus( appInstall, Status.INSTALLING ) Loading @@ -186,6 +187,6 @@ class InstallationProcessor @Inject constructor( } private suspend fun finishInstallation(appInstall: AppInstall, isUpdateWork: Boolean) { installationCompletionHandler.onInstallFinished(appInstall, isUpdateWork) installationCompletionNotifier.onInstallFinished(appInstall, isUpdateWork) } }
app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationCompletionHandler.kt +3 −2 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.wrapper.UpdatesNotificationSender import foundation.e.apps.data.install.wrapper.UpdatesTracker import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.installation.port.InstallationCompletionNotifier import foundation.e.apps.data.installation.repository.AppInstallRepository import foundation.e.apps.data.preference.PlayStoreAuthStore import foundation.e.apps.data.utils.getFormattedString Loading @@ -40,12 +41,12 @@ class InstallationCompletionHandler @Inject constructor( private val playStoreAuthStore: PlayStoreAuthStore, private val updatesTracker: UpdatesTracker, private val updatesNotificationSender: UpdatesNotificationSender, ) { ) : InstallationCompletionNotifier { companion object { private const val DATE_FORMAT = "dd/MM/yyyy-HH:mm" } suspend fun onInstallFinished(appInstall: AppInstall?, isUpdateWork: Boolean) { override suspend fun onInstallFinished(appInstall: AppInstall?, isUpdateWork: Boolean) { if (!isUpdateWork) { return } Loading
app/src/main/java/foundation/e/apps/data/install/download/DownloadManagerUtils.kt +3 −2 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.notification.StorageNotificationManager import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.installation.model.InstallationSource import foundation.e.apps.data.installation.port.InstallationDownloadStatusUpdater import foundation.e.apps.domain.model.install.Status import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi Loading @@ -48,7 +49,7 @@ class DownloadManagerUtils @Inject constructor( private val downloadManager: DownloadManager, private val storageNotificationManager: StorageNotificationManager, @IoCoroutineScope private val coroutineScope: CoroutineScope ) { ) : InstallationDownloadStatusUpdater { private val mutex = Mutex() @DelicateCoroutinesApi Loading @@ -60,7 +61,7 @@ class DownloadManagerUtils @Inject constructor( } @DelicateCoroutinesApi fun updateDownloadStatus(downloadId: Long) { override fun updateDownloadStatus(downloadId: Long) { coroutineScope.launch { mutex.withLock { // Waiting for DownloadManager to publish the progress of last bytes Loading