From e9d2f3e195ae1a69eb944d3bf7725797f855d7ad Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Thu, 12 Mar 2026 21:37:40 +0600 Subject: [PATCH 01/43] refactor: isolate AppInstallProcessor platform dependencies Created thin wrappers for global EventBus, storage, parental control auth checking, updates DAO, updates notifier, and network connectivity check. These wrappers replace direct static calls without changing existing behaviour and introduce mockable seams for tests in AppInstallProcessorTest. --- .../AppInstallProcessorBindingsModule.kt | 66 +++ .../workmanager/AppInstallProcessor.kt | 53 +-- .../install/wrapper/AppEventDispatcher.kt | 33 ++ .../install/wrapper/NetworkStatusChecker.kt | 36 ++ .../wrapper/ParentalControlAuthGateway.kt | 32 ++ .../install/wrapper/StorageSpaceChecker.kt | 33 ++ .../wrapper/UpdatesNotificationSender.kt | 36 ++ .../data/install/wrapper/UpdatesTracker.kt | 51 +++ .../AppInstallProcessorTest.kt | 386 ++++++++---------- 9 files changed, 482 insertions(+), 244 deletions(-) create mode 100644 app/src/main/java/foundation/e/apps/data/di/bindings/AppInstallProcessorBindingsModule.kt create mode 100644 app/src/main/java/foundation/e/apps/data/install/wrapper/AppEventDispatcher.kt create mode 100644 app/src/main/java/foundation/e/apps/data/install/wrapper/NetworkStatusChecker.kt create mode 100644 app/src/main/java/foundation/e/apps/data/install/wrapper/ParentalControlAuthGateway.kt create mode 100644 app/src/main/java/foundation/e/apps/data/install/wrapper/StorageSpaceChecker.kt create mode 100644 app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesNotificationSender.kt create mode 100644 app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesTracker.kt diff --git a/app/src/main/java/foundation/e/apps/data/di/bindings/AppInstallProcessorBindingsModule.kt b/app/src/main/java/foundation/e/apps/data/di/bindings/AppInstallProcessorBindingsModule.kt new file mode 100644 index 000000000..ed205c78b --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/di/bindings/AppInstallProcessorBindingsModule.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.data.di.bindings + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import foundation.e.apps.data.install.wrapper.AppEventDispatcher +import foundation.e.apps.data.install.wrapper.DefaultAppEventDispatcher +import foundation.e.apps.data.install.wrapper.DeviceNetworkStatusChecker +import foundation.e.apps.data.install.wrapper.NetworkStatusChecker +import foundation.e.apps.data.install.wrapper.ParentalControlAuthGateway +import foundation.e.apps.data.install.wrapper.ParentalControlAuthGatewayImpl +import foundation.e.apps.data.install.wrapper.StorageSpaceChecker +import foundation.e.apps.data.install.wrapper.StorageSpaceCheckerImpl +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 javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +interface AppInstallProcessorBindingsModule { + + @Binds + @Singleton + fun bindAppEventDispatcher(dispatcher: DefaultAppEventDispatcher): AppEventDispatcher + + @Binds + @Singleton + fun bindStorageSpaceChecker(checker: StorageSpaceCheckerImpl): StorageSpaceChecker + + @Binds + @Singleton + fun bindParentalControlAuthGateway(gateway: ParentalControlAuthGatewayImpl): ParentalControlAuthGateway + + @Binds + @Singleton + fun bindUpdatesTracker(tracker: UpdatesTrackerImpl): UpdatesTracker + + @Binds + @Singleton + fun bindUpdatesNotificationSender(sender: UpdatesNotificationSenderImpl): UpdatesNotificationSender + + @Binds + @Singleton + fun bindNetworkStatusChecker(checker: DeviceNetworkStatusChecker): NetworkStatusChecker +} 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 e2d33d90c..e65837d73 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 @@ -25,24 +25,24 @@ import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.application.ApplicationRepository -import foundation.e.apps.data.application.UpdatesDao import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Type import foundation.e.apps.data.event.AppEvent -import foundation.e.apps.data.event.EventBus import foundation.e.apps.data.install.AppInstallComponents import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.download.DownloadManagerUtils import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager -import foundation.e.apps.data.install.updates.UpdatesNotifier +import foundation.e.apps.data.install.wrapper.AppEventDispatcher +import foundation.e.apps.data.install.wrapper.NetworkStatusChecker +import foundation.e.apps.data.install.wrapper.ParentalControlAuthGateway +import foundation.e.apps.data.install.wrapper.StorageSpaceChecker +import foundation.e.apps.data.install.wrapper.UpdatesNotificationSender +import foundation.e.apps.data.install.wrapper.UpdatesTracker import foundation.e.apps.data.playstore.utils.GplayHttpRequestException import foundation.e.apps.data.preference.PlayStoreAuthStore -import foundation.e.apps.data.system.ParentalControlAuthenticator -import foundation.e.apps.data.system.StorageComputer -import foundation.e.apps.data.system.isNetworkAvailable import foundation.e.apps.data.utils.getFormattedString import foundation.e.apps.domain.ValidateAppAgeLimitUseCase import foundation.e.apps.domain.model.ContentRatingValidity @@ -66,6 +66,12 @@ class AppInstallProcessor @Inject constructor( private val sessionRepository: SessionRepository, private val playStoreAuthStore: PlayStoreAuthStore, private val storageNotificationManager: StorageNotificationManager, + private val appEventDispatcher: AppEventDispatcher, + private val storageSpaceChecker: StorageSpaceChecker, + private val parentalControlAuthGateway: ParentalControlAuthGateway, + private val updatesTracker: UpdatesTracker, + private val updatesNotificationSender: UpdatesNotificationSender, + private val networkStatusChecker: NetworkStatusChecker, ) { @Inject lateinit var downloadManager: DownloadManagerUtils @@ -139,7 +145,9 @@ class AppInstallProcessor @Inject constructor( if (!isSystemApp && (user == User.GOOGLE || user == User.ANONYMOUS)) { val authData = playStoreAuthStore.awaitAuthData() if (!appInstall.isFree && authData?.isAnonymous == true) { - EventBus.invokeEvent(AppEvent.ErrorMessageEvent(R.string.paid_app_anonymous_message)) + appEventDispatcher.dispatch( + AppEvent.ErrorMessageEvent(R.string.paid_app_anonymous_message) + ) } } @@ -175,17 +183,17 @@ class AppInstallProcessor @Inject constructor( return false } - if (!context.isNetworkAvailable()) { + if (!networkStatusChecker.isNetworkAvailable()) { appInstallComponents.appManagerWrapper.installationIssue(appInstall) - EventBus.invokeEvent(AppEvent.NoInternetEvent(false)) + appEventDispatcher.dispatch(AppEvent.NoInternetEvent(false)) return false } - if (StorageComputer.spaceMissing(appInstall) > 0) { + if (storageSpaceChecker.spaceMissing(appInstall) > 0) { Timber.d("Storage is not available for: ${appInstall.name} size: ${appInstall.appSize}") storageNotificationManager.showNotEnoughSpaceNotification(appInstall) appInstallComponents.appManagerWrapper.installationIssue(appInstall) - EventBus.invokeEvent(AppEvent.ErrorMessageEvent(R.string.not_enough_storage)) + appEventDispatcher.dispatch(AppEvent.ErrorMessageEvent(R.string.not_enough_storage)) return false } @@ -200,13 +208,13 @@ class AppInstallProcessor @Inject constructor( if (ageLimitValidationResult.isSuccess()) { awaitInvokeAgeLimitEvent(appInstall.name) if (ageLimitValidationResult.data?.requestPin == true) { - val isAuthenticated = ParentalControlAuthenticator.awaitAuthentication() + val isAuthenticated = parentalControlAuthGateway.awaitAuthentication() if (isAuthenticated) { ageLimitValidationResult.setData(ContentRatingValidity(true)) } } } else { - EventBus.invokeEvent(AppEvent.ErrorMessageDialogEvent(R.string.data_load_error_desc)) + appEventDispatcher.dispatch(AppEvent.ErrorMessageDialogEvent(R.string.data_load_error_desc)) } var ageIsValid = true @@ -219,7 +227,7 @@ class AppInstallProcessor @Inject constructor( suspend fun awaitInvokeAgeLimitEvent(type: String) { val deferred = CompletableDeferred() - EventBus.invokeEvent(AppEvent.AgeLimitRestrictionEvent(type, deferred)) + appEventDispatcher.dispatch(AppEvent.AgeLimitRestrictionEvent(type, deferred)) deferred.await() // await closing dialog box } @@ -233,7 +241,7 @@ class AppInstallProcessor @Inject constructor( return false } appInstallComponents.appManagerWrapper.addFusedDownloadPurchaseNeeded(appInstall) - EventBus.invokeEvent(AppEvent.AppPurchaseEvent(appInstall)) + appEventDispatcher.dispatch(AppEvent.AppPurchaseEvent(appInstall)) return false } catch (e: GplayHttpRequestException) { handleUpdateDownloadError( @@ -256,7 +264,7 @@ class AppInstallProcessor @Inject constructor( } private suspend fun handleAppRestricted(appInstall: AppInstall) { - EventBus.invokeEvent(AppEvent.AppRestrictedOrUnavailable(appInstall)) + appEventDispatcher.dispatch(AppEvent.AppRestrictedOrUnavailable(appInstall)) appManager.addDownload(appInstall) appManager.updateUnavailable(appInstall) } @@ -267,7 +275,7 @@ class AppInstallProcessor @Inject constructor( e: Exception ) { Timber.e(e, "Updating download Urls failed for $message") - EventBus.invokeEvent( + appEventDispatcher.dispatch( AppEvent.UpdateEvent( ResultSupreme.WorkError( ResultStatus.UNKNOWN, @@ -367,12 +375,12 @@ class AppInstallProcessor @Inject constructor( appInstallComponents.appManagerWrapper.getFusedDownloadPackageStatus(appInstall) if (packageStatus == Status.INSTALLED) { - UpdatesDao.addSuccessfullyUpdatedApp(it) + updatesTracker.addSuccessfullyUpdatedApp(it) } if (isUpdateCompleted()) { // show notification for ended update showNotificationOnUpdateEnded() - UpdatesDao.clearSuccessfullyUpdatedApps() + updatesTracker.clearSuccessfullyUpdatedApps() } } } @@ -387,18 +395,17 @@ class AppInstallProcessor @Inject constructor( ).contains(it.status) } - return UpdatesDao.successfulUpdatedApps.isNotEmpty() && downloadListWithoutAnyIssue.isEmpty() + return updatesTracker.hasSuccessfulUpdatedApps() && downloadListWithoutAnyIssue.isEmpty() } private suspend fun showNotificationOnUpdateEnded() { val locale = playStoreAuthStore.awaitAuthData()?.locale ?: java.util.Locale.getDefault() val date = Date().getFormattedString(DATE_FORMAT, locale) val numberOfUpdatedApps = - NumberFormat.getNumberInstance(locale).format(UpdatesDao.successfulUpdatedApps.size) + NumberFormat.getNumberInstance(locale).format(updatesTracker.successfulUpdatedAppsCount()) .toString() - UpdatesNotifier.showNotification( - context, + updatesNotificationSender.showNotification( context.getString(R.string.update), context.getString( R.string.message_last_update_triggered, diff --git a/app/src/main/java/foundation/e/apps/data/install/wrapper/AppEventDispatcher.kt b/app/src/main/java/foundation/e/apps/data/install/wrapper/AppEventDispatcher.kt new file mode 100644 index 000000000..e9c44ea58 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/install/wrapper/AppEventDispatcher.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.data.install.wrapper + +import foundation.e.apps.data.event.AppEvent +import foundation.e.apps.data.event.EventBus +import javax.inject.Inject + +interface AppEventDispatcher { + suspend fun dispatch(event: AppEvent) +} + +class DefaultAppEventDispatcher @Inject constructor() : AppEventDispatcher { + override suspend fun dispatch(event: AppEvent) { + EventBus.invokeEvent(event) + } +} diff --git a/app/src/main/java/foundation/e/apps/data/install/wrapper/NetworkStatusChecker.kt b/app/src/main/java/foundation/e/apps/data/install/wrapper/NetworkStatusChecker.kt new file mode 100644 index 000000000..612ed2b0c --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/install/wrapper/NetworkStatusChecker.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.data.install.wrapper + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.apps.data.system.isNetworkAvailable +import javax.inject.Inject + +interface NetworkStatusChecker { + fun isNetworkAvailable(): Boolean +} + +class DeviceNetworkStatusChecker @Inject constructor( + @ApplicationContext private val context: Context +) : NetworkStatusChecker { + override fun isNetworkAvailable(): Boolean { + return context.isNetworkAvailable() + } +} diff --git a/app/src/main/java/foundation/e/apps/data/install/wrapper/ParentalControlAuthGateway.kt b/app/src/main/java/foundation/e/apps/data/install/wrapper/ParentalControlAuthGateway.kt new file mode 100644 index 000000000..36829868b --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/install/wrapper/ParentalControlAuthGateway.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.data.install.wrapper + +import foundation.e.apps.data.system.ParentalControlAuthenticator +import javax.inject.Inject + +interface ParentalControlAuthGateway { + suspend fun awaitAuthentication(): Boolean +} + +class ParentalControlAuthGatewayImpl @Inject constructor() : ParentalControlAuthGateway { + override suspend fun awaitAuthentication(): Boolean { + return ParentalControlAuthenticator.awaitAuthentication() + } +} diff --git a/app/src/main/java/foundation/e/apps/data/install/wrapper/StorageSpaceChecker.kt b/app/src/main/java/foundation/e/apps/data/install/wrapper/StorageSpaceChecker.kt new file mode 100644 index 000000000..e4b330af9 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/install/wrapper/StorageSpaceChecker.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.data.install.wrapper + +import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.system.StorageComputer +import javax.inject.Inject + +interface StorageSpaceChecker { + fun spaceMissing(appInstall: AppInstall): Long +} + +class StorageSpaceCheckerImpl @Inject constructor() : StorageSpaceChecker { + override fun spaceMissing(appInstall: AppInstall): Long { + return StorageComputer.spaceMissing(appInstall) + } +} diff --git a/app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesNotificationSender.kt b/app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesNotificationSender.kt new file mode 100644 index 000000000..97a226c93 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesNotificationSender.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.data.install.wrapper + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.apps.data.install.updates.UpdatesNotifier +import javax.inject.Inject + +interface UpdatesNotificationSender { + fun showNotification(title: String, message: String) +} + +class UpdatesNotificationSenderImpl @Inject constructor( + @ApplicationContext private val context: Context +) : UpdatesNotificationSender { + override fun showNotification(title: String, message: String) { + UpdatesNotifier.showNotification(context, title, message) + } +} diff --git a/app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesTracker.kt b/app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesTracker.kt new file mode 100644 index 000000000..26bf2e1ba --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesTracker.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.data.install.wrapper + +import foundation.e.apps.data.application.UpdatesDao +import foundation.e.apps.data.install.models.AppInstall +import javax.inject.Inject + +interface UpdatesTracker { + fun addSuccessfullyUpdatedApp(appInstall: AppInstall) + + fun clearSuccessfullyUpdatedApps() + + fun hasSuccessfulUpdatedApps(): Boolean + + fun successfulUpdatedAppsCount(): Int +} + +class UpdatesTrackerImpl @Inject constructor() : UpdatesTracker { + override fun addSuccessfullyUpdatedApp(appInstall: AppInstall) { + UpdatesDao.addSuccessfullyUpdatedApp(appInstall) + } + + override fun clearSuccessfullyUpdatedApps() { + UpdatesDao.clearSuccessfullyUpdatedApps() + } + + override fun hasSuccessfulUpdatedApps(): Boolean { + return UpdatesDao.successfulUpdatedApps.isNotEmpty() + } + + override fun successfulUpdatedAppsCount(): Int { + return UpdatesDao.successfulUpdatedApps.size + } +} diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt index 84e01ea3c..295d99f92 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt @@ -19,9 +19,6 @@ package foundation.e.apps.installProcessor import android.content.Context -import android.net.ConnectivityManager -import android.net.Network -import android.net.NetworkCapabilities import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.work.Operation import com.google.common.util.concurrent.Futures @@ -40,9 +37,14 @@ import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager import foundation.e.apps.data.install.workmanager.AppInstallProcessor -import foundation.e.apps.data.preference.PlayStoreAuthStore import foundation.e.apps.data.install.workmanager.InstallWorkManager -import foundation.e.apps.data.system.StorageComputer +import foundation.e.apps.data.install.wrapper.AppEventDispatcher +import foundation.e.apps.data.install.wrapper.NetworkStatusChecker +import foundation.e.apps.data.install.wrapper.ParentalControlAuthGateway +import foundation.e.apps.data.install.wrapper.StorageSpaceChecker +import foundation.e.apps.data.install.wrapper.UpdatesNotificationSender +import foundation.e.apps.data.install.wrapper.UpdatesTracker +import foundation.e.apps.data.preference.PlayStoreAuthStore import foundation.e.apps.domain.ValidateAppAgeLimitUseCase import foundation.e.apps.domain.model.ContentRatingValidity import foundation.e.apps.domain.preferences.SessionRepository @@ -110,11 +112,24 @@ class AppInstallProcessorTest { @Mock private lateinit var storageNotificationManager: StorageNotificationManager + private lateinit var appEventDispatcher: AppEventDispatcher + private lateinit var storageSpaceChecker: StorageSpaceChecker + private lateinit var parentalControlAuthGateway: ParentalControlAuthGateway + private lateinit var updatesTracker: UpdatesTracker + private lateinit var updatesNotificationSender: UpdatesNotificationSender + private lateinit var networkStatusChecker: NetworkStatusChecker + private var isInstallWorkManagerMocked = false @Before fun setup() { MockitoAnnotations.openMocks(this) + appEventDispatcher = mockk(relaxed = true) + storageSpaceChecker = mockk(relaxed = true) + parentalControlAuthGateway = mockk(relaxed = true) + updatesTracker = mockk(relaxed = true) + updatesNotificationSender = mockk(relaxed = true) + networkStatusChecker = mockk(relaxed = true) fakeFusedDownloadDAO = FakeAppInstallDAO() appInstallRepository = AppInstallRepository(fakeFusedDownloadDAO) fakeFusedManagerRepository = @@ -129,7 +144,13 @@ class AppInstallProcessorTest { validateAppAgeRatingUseCase, sessionRepository, playStoreAuthStore, - storageNotificationManager + storageNotificationManager, + appEventDispatcher, + storageSpaceChecker, + parentalControlAuthGateway, + updatesTracker, + updatesNotificationSender, + networkStatusChecker ) } @@ -256,21 +277,16 @@ class AppInstallProcessorTest { val appManagerWrapper = mockk(relaxed = true) val processor = createProcessorForCanEnqueue(appManagerWrapper) - mockkObject(StorageComputer) - try { - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - everyNetworkAvailable() - every { StorageComputer.spaceMissing(appInstall) } returns 0 + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) + .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) + every { networkStatusChecker.isNetworkAvailable() } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0 - val result = processor.canEnqueue(appInstall) + val result = processor.canEnqueue(appInstall) - assertTrue(result) - coVerify { appManagerWrapper.addDownload(appInstall) } - } finally { - unmockkObject(StorageComputer) - } + assertTrue(result) + coVerify { appManagerWrapper.addDownload(appInstall) } } @Test @@ -286,21 +302,16 @@ class AppInstallProcessorTest { val appManagerWrapper = mockk(relaxed = true) val processor = createProcessorForCanEnqueue(appManagerWrapper) - mockkObject(StorageComputer) - try { - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - everyNetworkUnavailable() - every { StorageComputer.spaceMissing(appInstall) } returns 0 + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) + .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) + every { networkStatusChecker.isNetworkAvailable() } returns false + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0 - val result = processor.canEnqueue(appInstall) + val result = processor.canEnqueue(appInstall) - assertEquals(false, result) - coVerify { appManagerWrapper.installationIssue(appInstall) } - } finally { - unmockkObject(StorageComputer) - } + assertEquals(false, result) + coVerify { appManagerWrapper.installationIssue(appInstall) } } @Test @@ -316,22 +327,17 @@ class AppInstallProcessorTest { val appManagerWrapper = mockk(relaxed = true) val processor = createProcessorForCanEnqueue(appManagerWrapper) - mockkObject(StorageComputer) - try { - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - everyNetworkAvailable() - every { StorageComputer.spaceMissing(appInstall) } returns 100L - - val result = processor.canEnqueue(appInstall) - - assertEquals(false, result) - Mockito.verify(storageNotificationManager).showNotEnoughSpaceNotification(appInstall) - coVerify { appManagerWrapper.installationIssue(appInstall) } - } finally { - unmockkObject(StorageComputer) - } + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) + .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) + every { networkStatusChecker.isNetworkAvailable() } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 100L + + val result = processor.canEnqueue(appInstall) + + assertEquals(false, result) + Mockito.verify(storageNotificationManager).showNotEnoughSpaceNotification(appInstall) + coVerify { appManagerWrapper.installationIssue(appInstall) } } @Test @@ -347,21 +353,16 @@ class AppInstallProcessorTest { val appManagerWrapper = mockk(relaxed = true) val processor = createProcessorForCanEnqueue(appManagerWrapper) - mockkObject(StorageComputer) - try { - coEvery { appManagerWrapper.addDownload(appInstall) } returns false - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - everyNetworkAvailable() - every { StorageComputer.spaceMissing(appInstall) } returns 0L + coEvery { appManagerWrapper.addDownload(appInstall) } returns false + Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) + .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) + every { networkStatusChecker.isNetworkAvailable() } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L - val result = processor.canEnqueue(appInstall) + val result = processor.canEnqueue(appInstall) - assertEquals(false, result) - coVerify(exactly = 0) { appManagerWrapper.installationIssue(appInstall) } - } finally { - unmockkObject(StorageComputer) - } + assertEquals(false, result) + coVerify(exactly = 0) { appManagerWrapper.installationIssue(appInstall) } } @Test @@ -377,21 +378,16 @@ class AppInstallProcessorTest { val appManagerWrapper = mockk(relaxed = true) val processor = createProcessorForCanEnqueue(appManagerWrapper) - mockkObject(StorageComputer) - try { - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.UNKNOWN, ContentRatingValidity(false))) - everyNetworkAvailable() - every { StorageComputer.spaceMissing(appInstall) } returns 0L + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) + .thenReturn(ResultSupreme.create(ResultStatus.UNKNOWN, ContentRatingValidity(false))) + every { networkStatusChecker.isNetworkAvailable() } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L - val result = processor.canEnqueue(appInstall) + val result = processor.canEnqueue(appInstall) - assertEquals(false, result) - coVerify { appManagerWrapper.cancelDownload(appInstall) } - } finally { - unmockkObject(StorageComputer) - } + assertEquals(false, result) + coVerify { appManagerWrapper.cancelDownload(appInstall) } } @Test @@ -401,22 +397,17 @@ class AppInstallProcessorTest { val processor = createProcessorForCanEnqueue(appManagerWrapper) mockInstallWorkManagerSuccess() - mockkObject(StorageComputer) - try { - everyNetworkAvailable() - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - every { StorageComputer.spaceMissing(appInstall) } returns 0L - - val result = processor.enqueueFusedDownload(appInstall, isAnUpdate = true, isSystemApp = true) - - assertTrue(result) - coVerify { appManagerWrapper.updateAwaiting(appInstall) } - verify(exactly = 1) { InstallWorkManager.enqueueWork(context, appInstall, true) } - } finally { - unmockkObject(StorageComputer) - } + every { networkStatusChecker.isNetworkAvailable() } returns true + Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) + .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + + val result = processor.enqueueFusedDownload(appInstall, isAnUpdate = true, isSystemApp = true) + + assertTrue(result) + coVerify { appManagerWrapper.updateAwaiting(appInstall) } + verify(exactly = 1) { InstallWorkManager.enqueueWork(context, appInstall, true) } } @Test @@ -426,22 +417,17 @@ class AppInstallProcessorTest { val processor = createProcessorForCanEnqueue(appManagerWrapper) mockInstallWorkManagerFailure() - mockkObject(StorageComputer) - try { - everyNetworkAvailable() - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - every { StorageComputer.spaceMissing(appInstall) } returns 0L - - val result = processor.enqueueFusedDownload(appInstall, isAnUpdate = true, isSystemApp = true) - - assertEquals(false, result) - coVerify { appManagerWrapper.updateAwaiting(appInstall) } - coVerify { appManagerWrapper.installationIssue(appInstall) } - } finally { - unmockkObject(StorageComputer) - } + every { networkStatusChecker.isNetworkAvailable() } returns true + Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) + .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + + val result = processor.enqueueFusedDownload(appInstall, isAnUpdate = true, isSystemApp = true) + + assertEquals(false, result) + coVerify { appManagerWrapper.updateAwaiting(appInstall) } + coVerify { appManagerWrapper.installationIssue(appInstall) } } @Test @@ -451,22 +437,17 @@ class AppInstallProcessorTest { val processor = createProcessorForCanEnqueue(appManagerWrapper) mockInstallWorkManagerSuccess() - mockkObject(StorageComputer) - try { - everyNetworkAvailable() - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - every { StorageComputer.spaceMissing(appInstall) } returns 0L - - val result = processor.enqueueFusedDownload(appInstall, isAnUpdate = false, isSystemApp = true) - - assertTrue(result) - coVerify { appManagerWrapper.updateAwaiting(appInstall) } - verify(exactly = 0) { InstallWorkManager.enqueueWork(any(), any(), any()) } - } finally { - unmockkObject(StorageComputer) - } + every { networkStatusChecker.isNetworkAvailable() } returns true + Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) + .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + + val result = processor.enqueueFusedDownload(appInstall, isAnUpdate = false, isSystemApp = true) + + assertTrue(result) + coVerify { appManagerWrapper.updateAwaiting(appInstall) } + verify(exactly = 0) { InstallWorkManager.enqueueWork(any(), any(), any()) } } @Test @@ -476,22 +457,17 @@ class AppInstallProcessorTest { val processor = createProcessorForCanEnqueue(appManagerWrapper) mockInstallWorkManagerFailure() - mockkObject(StorageComputer) - try { - everyNetworkAvailable() - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - every { StorageComputer.spaceMissing(appInstall) } returns 0L - - val result = processor.enqueueFusedDownload(appInstall, isAnUpdate = false, isSystemApp = true) - - assertTrue(result) - verify(exactly = 0) { InstallWorkManager.enqueueWork(any(), any(), any()) } - coVerify(exactly = 0) { appManagerWrapper.installationIssue(appInstall) } - } finally { - unmockkObject(StorageComputer) - } + every { networkStatusChecker.isNetworkAvailable() } returns true + Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) + .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + + val result = processor.enqueueFusedDownload(appInstall, isAnUpdate = false, isSystemApp = true) + + assertTrue(result) + verify(exactly = 0) { InstallWorkManager.enqueueWork(any(), any(), any()) } + coVerify(exactly = 0) { appManagerWrapper.installationIssue(appInstall) } } @Test @@ -502,22 +478,17 @@ class AppInstallProcessorTest { val processor = createProcessorForCanEnqueue(appManagerWrapper) mockInstallWorkManagerSuccess() - mockkObject(StorageComputer) - try { - everyNetworkAvailable() - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - every { appManagerWrapper.isFusedDownloadInstalled(appInstall) } returns false - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - every { StorageComputer.spaceMissing(appInstall) } returns 0L - - val result = processor.initAppInstall(application, isAnUpdate = true) - - assertTrue(result) - verify(exactly = 1) { InstallWorkManager.enqueueWork(context, appInstall, true) } - } finally { - unmockkObject(StorageComputer) - } + every { networkStatusChecker.isNetworkAvailable() } returns true + Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) + .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) + every { appManagerWrapper.isFusedDownloadInstalled(appInstall) } returns false + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + + val result = processor.initAppInstall(application, isAnUpdate = true) + + assertTrue(result) + verify(exactly = 1) { InstallWorkManager.enqueueWork(context, appInstall, true) } } @Test @@ -528,22 +499,17 @@ class AppInstallProcessorTest { val processor = createProcessorForCanEnqueue(appManagerWrapper) mockInstallWorkManagerSuccess() - mockkObject(StorageComputer) - try { - everyNetworkAvailable() - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - every { appManagerWrapper.isFusedDownloadInstalled(appInstall) } returns false - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - every { StorageComputer.spaceMissing(appInstall) } returns 0L - - val result = processor.initAppInstall(application, isAnUpdate = false) - - assertTrue(result) - verify(exactly = 1) { InstallWorkManager.enqueueWork(context, appInstall, true) } - } finally { - unmockkObject(StorageComputer) - } + every { networkStatusChecker.isNetworkAvailable() } returns true + Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) + .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) + every { appManagerWrapper.isFusedDownloadInstalled(appInstall) } returns false + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + + val result = processor.initAppInstall(application, isAnUpdate = false) + + assertTrue(result) + verify(exactly = 1) { InstallWorkManager.enqueueWork(context, appInstall, true) } } @Test @@ -554,22 +520,17 @@ class AppInstallProcessorTest { val processor = createProcessorForCanEnqueue(appManagerWrapper) mockInstallWorkManagerSuccess() - mockkObject(StorageComputer) - try { - everyNetworkAvailable() - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - every { appManagerWrapper.isFusedDownloadInstalled(appInstall) } returns true - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - every { StorageComputer.spaceMissing(appInstall) } returns 0L - - val result = processor.initAppInstall(application, isAnUpdate = false) - - assertTrue(result) - verify(exactly = 1) { InstallWorkManager.enqueueWork(context, appInstall, true) } - } finally { - unmockkObject(StorageComputer) - } + every { networkStatusChecker.isNetworkAvailable() } returns true + Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) + .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) + every { appManagerWrapper.isFusedDownloadInstalled(appInstall) } returns true + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + + val result = processor.initAppInstall(application, isAnUpdate = false) + + assertTrue(result) + verify(exactly = 1) { InstallWorkManager.enqueueWork(context, appInstall, true) } } @Test @@ -580,22 +541,17 @@ class AppInstallProcessorTest { val processor = createProcessorForCanEnqueue(appManagerWrapper) mockInstallWorkManagerFailure() - mockkObject(StorageComputer) - try { - everyNetworkAvailable() - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - every { appManagerWrapper.isFusedDownloadInstalled(appInstall) } returns false - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - every { StorageComputer.spaceMissing(appInstall) } returns 0L - - val result = processor.initAppInstall(application, isAnUpdate = false) - - assertTrue(result) - verify(exactly = 0) { InstallWorkManager.enqueueWork(any(), any(), any()) } - } finally { - unmockkObject(StorageComputer) - } + every { networkStatusChecker.isNetworkAvailable() } returns true + Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) + .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) + every { appManagerWrapper.isFusedDownloadInstalled(appInstall) } returns false + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + + val result = processor.initAppInstall(application, isAnUpdate = false) + + assertTrue(result) + verify(exactly = 0) { InstallWorkManager.enqueueWork(any(), any(), any()) } } private suspend fun runProcessInstall(appInstall: AppInstall): AppInstall? { @@ -627,7 +583,13 @@ class AppInstallProcessorTest { validateAppAgeRatingUseCase, sessionRepository, playStoreAuthStore, - storageNotificationManager + storageNotificationManager, + appEventDispatcher, + storageSpaceChecker, + parentalControlAuthGateway, + updatesTracker, + updatesNotificationSender, + networkStatusChecker ) } @@ -695,22 +657,4 @@ class AppInstallProcessorTest { return operation } - private fun everyNetworkAvailable() { - val connectivityManager = mock() - val network = mock() - val networkCapabilities = mock() - whenever(context.getSystemService(ConnectivityManager::class.java)).thenReturn(connectivityManager) - whenever(connectivityManager.activeNetwork).thenReturn(network) - whenever(connectivityManager.getNetworkCapabilities(network)).thenReturn(networkCapabilities) - whenever(networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)).thenReturn(true) - whenever(networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)).thenReturn(true) - } - - private fun everyNetworkUnavailable() { - val connectivityManager = mock() - val network = mock() - whenever(context.getSystemService(ConnectivityManager::class.java)).thenReturn(connectivityManager) - whenever(connectivityManager.activeNetwork).thenReturn(network) - whenever(connectivityManager.getNetworkCapabilities(network)).thenReturn(null) - } } -- GitLab From a04669b7bbcb0dd2c421691c05ed4b0eeb680eb3 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Thu, 12 Mar 2026 21:48:33 +0600 Subject: [PATCH 02/43] test: capture AppInstallProcessor baseline tests for existing behaviour Lock down the current enqueue, worker, and update-completion quirks before extraction so later class splits can be checked against explicit behavior instead of assumptions. --- .../AppInstallProcessorTest.kt | 249 +++++++++++++++++- 1 file changed, 240 insertions(+), 9 deletions(-) diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt index 295d99f92..ec07912b7 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt @@ -21,13 +21,16 @@ package foundation.e.apps.installProcessor import android.content.Context import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.work.Operation +import com.aurora.gplayapi.data.models.AuthData import com.google.common.util.concurrent.Futures +import foundation.e.apps.R import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.application.ApplicationRepository -import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.application.data.Application +import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Type +import foundation.e.apps.data.event.AppEvent import foundation.e.apps.domain.model.install.Status import foundation.e.apps.data.fdroid.FDroidRepository import foundation.e.apps.data.install.AppInstallComponents @@ -44,18 +47,21 @@ import foundation.e.apps.data.install.wrapper.ParentalControlAuthGateway import foundation.e.apps.data.install.wrapper.StorageSpaceChecker import foundation.e.apps.data.install.wrapper.UpdatesNotificationSender import foundation.e.apps.data.install.wrapper.UpdatesTracker +import foundation.e.apps.data.playstore.utils.GplayHttpRequestException import foundation.e.apps.data.preference.PlayStoreAuthStore import foundation.e.apps.domain.ValidateAppAgeLimitUseCase import foundation.e.apps.domain.model.ContentRatingValidity +import foundation.e.apps.domain.model.User import foundation.e.apps.domain.preferences.SessionRepository import foundation.e.apps.util.MainCoroutineRule -import io.mockk.every import io.mockk.coEvery import io.mockk.coVerify -import io.mockk.verify +import io.mockk.every +import io.mockk.justRun import io.mockk.mockk import io.mockk.mockkObject import io.mockk.unmockkObject +import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals @@ -69,6 +75,7 @@ import org.mockito.Mockito import org.mockito.MockitoAnnotations import org.mockito.kotlin.mock import org.mockito.kotlin.whenever +import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) class AppInstallProcessorTest { @@ -92,16 +99,12 @@ class AppInstallProcessorTest { @Mock private lateinit var fakeFDroidRepository: FDroidRepository - @Mock private lateinit var context: Context - @Mock private lateinit var sessionRepository: SessionRepository - @Mock private lateinit var playStoreAuthStore: PlayStoreAuthStore - @Mock private lateinit var applicationRepository: ApplicationRepository private lateinit var appInstallProcessor: AppInstallProcessor @@ -112,7 +115,7 @@ class AppInstallProcessorTest { @Mock private lateinit var storageNotificationManager: StorageNotificationManager - private lateinit var appEventDispatcher: AppEventDispatcher + private lateinit var appEventDispatcher: RecordingAppEventDispatcher private lateinit var storageSpaceChecker: StorageSpaceChecker private lateinit var parentalControlAuthGateway: ParentalControlAuthGateway private lateinit var updatesTracker: UpdatesTracker @@ -124,7 +127,13 @@ class AppInstallProcessorTest { @Before fun setup() { MockitoAnnotations.openMocks(this) - appEventDispatcher = mockk(relaxed = true) + context = mockk(relaxed = true) + sessionRepository = mockk(relaxed = true) + playStoreAuthStore = mockk(relaxed = true) + applicationRepository = mockk(relaxed = true) + coEvery { sessionRepository.awaitUser() } returns User.NO_GOOGLE + coEvery { playStoreAuthStore.awaitAuthData() } returns null + appEventDispatcher = RecordingAppEventDispatcher() storageSpaceChecker = mockk(relaxed = true) parentalControlAuthGateway = mockk(relaxed = true) updatesTracker = mockk(relaxed = true) @@ -264,6 +273,185 @@ class AppInstallProcessorTest { assertEquals("processInstall", finalFusedDownload, null) } + @Test + fun `enqueueFusedDownload warns anonymous paid users without aborting`() = runTest { + val appInstall = AppInstall( + type = foundation.e.apps.data.enums.Type.PWA, + id = "123", + status = Status.AWAITING, + downloadURLList = mutableListOf("apk"), + packageName = "com.example.paid", + isFree = false + ) + val appManagerWrapper = mockk(relaxed = true) + val processor = createProcessorForCanEnqueue(appManagerWrapper) + + mockkObject(InstallWorkManager) + try { + coEvery { sessionRepository.awaitUser() } returns User.ANONYMOUS + coEvery { + playStoreAuthStore.awaitAuthData() + } returns AuthData(email = "anon@example.com", isAnonymous = true) + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) + .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) + every { networkStatusChecker.isNetworkAvailable() } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + justRun { InstallWorkManager.enqueueWork(any(), any(), any()) } + + val result = processor.enqueueFusedDownload(appInstall) + + assertTrue(result) + assertTrue(appEventDispatcher.events.any { + it is AppEvent.ErrorMessageEvent && it.data == R.string.paid_app_anonymous_message + }) + coVerify { appManagerWrapper.updateAwaiting(appInstall) } + verify(exactly = 0) { InstallWorkManager.enqueueWork(any(), any(), any()) } + } finally { + unmockkObject(InstallWorkManager) + } + } + + @Test + fun `canEnqueue refreshes download urls for non-PWA installs`() = runTest { + val appInstall = AppInstall( + type = foundation.e.apps.data.enums.Type.NATIVE, + source = Source.PLAY_STORE, + id = "123", + status = Status.AWAITING, + packageName = "com.example.app" + ) + + val appManagerWrapper = mockk(relaxed = true) + val processor = createProcessorForCanEnqueue(appManagerWrapper) + + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) + .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) + every { networkStatusChecker.isNetworkAvailable() } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + + val result = processor.canEnqueue(appInstall) + + assertTrue(result) + coVerify { applicationRepository.updateFusedDownloadWithDownloadingInfo(Source.PLAY_STORE, appInstall) } + coVerify { appManagerWrapper.addDownload(appInstall) } + } + + @Test + fun `canEnqueue returns false when download url refresh throws http error`() = runTest { + val appInstall = AppInstall( + type = foundation.e.apps.data.enums.Type.NATIVE, + source = Source.PLAY_STORE, + id = "123", + status = Status.AWAITING, + packageName = "com.example.app" + ) + + val appManagerWrapper = mockk(relaxed = true) + val processor = createProcessorForCanEnqueue(appManagerWrapper) + + coEvery { + applicationRepository.updateFusedDownloadWithDownloadingInfo(Source.PLAY_STORE, appInstall) + } throws GplayHttpRequestException(403, "forbidden") + + val result = processor.canEnqueue(appInstall) + + assertEquals(false, result) + assertTrue(appEventDispatcher.events.any { it is AppEvent.UpdateEvent }) + coVerify(exactly = 0) { appManagerWrapper.addDownload(appInstall) } + } + + @Test + fun `canEnqueue keeps going when download url refresh throws illegal state`() = runTest { + val appInstall = AppInstall( + type = foundation.e.apps.data.enums.Type.NATIVE, + source = Source.PLAY_STORE, + id = "123", + status = Status.AWAITING, + packageName = "com.example.app" + ) + + val appManagerWrapper = mockk(relaxed = true) + val processor = createProcessorForCanEnqueue(appManagerWrapper) + + coEvery { + applicationRepository.updateFusedDownloadWithDownloadingInfo(Source.PLAY_STORE, appInstall) + } throws IllegalStateException("boom") + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) + .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) + every { networkStatusChecker.isNetworkAvailable() } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + + val result = processor.canEnqueue(appInstall) + + assertTrue(result) + coVerify { appManagerWrapper.addDownload(appInstall) } + } + + @Test + fun `processInstall returns success when internal exception occurs`() = runTest { + val fusedDownload = initTest() + fakeFusedManagerRepository.forceCrash = true + + val result = appInstallProcessor.processInstall(fusedDownload.id, false) { + // _ignored_ + } + + assertTrue(result.isSuccess) + assertEquals(ResultStatus.OK, result.getOrNull()) + } + + @Test + fun `processInstall enters foreground before download starts`() = runTest { + val fusedDownload = initTest() + var statusAtForeground: Status? = null + + appInstallProcessor.processInstall(fusedDownload.id, false) { + statusAtForeground = fusedDownload.status + } + + assertEquals(Status.AWAITING, statusAtForeground) + } + + @Test + fun `processInstall update completion ignores installation issues`() = runTest { + stubUpdateNotificationContext() + every { updatesTracker.hasSuccessfulUpdatedApps() } returns true + every { updatesTracker.successfulUpdatedAppsCount() } returns 1 + + processCompletedUpdate(Status.INSTALLATION_ISSUE) + + verify { updatesTracker.addSuccessfullyUpdatedApp(any()) } + verify { updatesNotificationSender.showNotification("Update", "Updated message") } + verify { updatesTracker.clearSuccessfullyUpdatedApps() } + } + + @Test + fun `processInstall update completion ignores purchase needed`() = runTest { + stubUpdateNotificationContext() + every { updatesTracker.hasSuccessfulUpdatedApps() } returns true + every { updatesTracker.successfulUpdatedAppsCount() } returns 1 + + processCompletedUpdate(Status.PURCHASE_NEEDED) + + verify { updatesTracker.addSuccessfullyUpdatedApp(any()) } + verify { updatesNotificationSender.showNotification("Update", "Updated message") } + verify { updatesTracker.clearSuccessfullyUpdatedApps() } + } + + @Test + fun `processInstall clears tracked updates after final notification`() = runTest { + stubUpdateNotificationContext() + every { updatesTracker.hasSuccessfulUpdatedApps() } returns true + every { updatesTracker.successfulUpdatedAppsCount() } returns 1 + + processCompletedUpdate() + + verify { updatesTracker.clearSuccessfullyUpdatedApps() } + } + @Test fun canEnqueue_returnsTrueWhenAllChecksPass() = runTest { val appInstall = AppInstall( @@ -561,6 +749,41 @@ class AppInstallProcessorTest { return fakeFusedDownloadDAO.getDownloadById(appInstall.id) } + private suspend fun processCompletedUpdate(ignoredStatus: Status? = null) { + val fusedDownload = initTest() + fakeFusedManagerRepository.isAppInstalled = true + + ignoredStatus?.let { + fakeFusedDownloadDAO.addDownload( + AppInstall( + id = "ignored-$it", + status = it, + downloadURLList = mutableListOf("apk"), + packageName = "com.example.$it" + ) + ) + } + + appInstallProcessor.processInstall(fusedDownload.id, true) { + // _ignored_ + } + } + + private suspend fun stubUpdateNotificationContext() { + val authData = AuthData(email = "user@example.com", isAnonymous = false).apply { + locale = Locale.US + } + coEvery { playStoreAuthStore.awaitAuthData() } returns authData + every { context.getString(R.string.update) } returns "Update" + every { + context.getString( + R.string.message_last_update_triggered, + any(), + any() + ) + } returns "Updated message" + } + private fun createFusedDownload( packageName: String? = null, downloadUrlList: MutableList? = null @@ -658,3 +881,11 @@ class AppInstallProcessorTest { } } + +private class RecordingAppEventDispatcher : AppEventDispatcher { + val events = mutableListOf() + + override suspend fun dispatch(event: AppEvent) { + events.add(event) + } +} -- GitLab From a35e96c139fa79b587fa518c7eb6149774f93833 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Thu, 12 Mar 2026 22:27:54 +0600 Subject: [PATCH 03/43] refactor: extract update completion handling from AppInstallProcessor Move update bookkeeping and final notification logic behind AppUpdateCompletionHandler so the worker flow can keep its current behavior while the update rules gain their own test coverage. --- .../workmanager/AppInstallProcessor.kt | 54 +----- .../workmanager/AppUpdateCompletionHandler.kt | 93 ++++++++++ .../AppInstallProcessorTest.kt | 20 ++- .../AppUpdateCompletionHandlerTest.kt | 166 ++++++++++++++++++ 4 files changed, 275 insertions(+), 58 deletions(-) create mode 100644 app/src/main/java/foundation/e/apps/data/install/workmanager/AppUpdateCompletionHandler.kt create mode 100644 app/src/test/java/foundation/e/apps/installProcessor/AppUpdateCompletionHandlerTest.kt 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 e65837d73..3457354ca 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 @@ -39,11 +39,8 @@ import foundation.e.apps.data.install.wrapper.AppEventDispatcher import foundation.e.apps.data.install.wrapper.NetworkStatusChecker import foundation.e.apps.data.install.wrapper.ParentalControlAuthGateway import foundation.e.apps.data.install.wrapper.StorageSpaceChecker -import foundation.e.apps.data.install.wrapper.UpdatesNotificationSender -import foundation.e.apps.data.install.wrapper.UpdatesTracker import foundation.e.apps.data.playstore.utils.GplayHttpRequestException import foundation.e.apps.data.preference.PlayStoreAuthStore -import foundation.e.apps.data.utils.getFormattedString import foundation.e.apps.domain.ValidateAppAgeLimitUseCase import foundation.e.apps.domain.model.ContentRatingValidity import foundation.e.apps.domain.model.User @@ -53,8 +50,6 @@ import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.flow.transformWhile import timber.log.Timber -import java.text.NumberFormat -import java.util.Date import javax.inject.Inject @Suppress("LongParameterList") @@ -69,9 +64,8 @@ class AppInstallProcessor @Inject constructor( private val appEventDispatcher: AppEventDispatcher, private val storageSpaceChecker: StorageSpaceChecker, private val parentalControlAuthGateway: ParentalControlAuthGateway, - private val updatesTracker: UpdatesTracker, - private val updatesNotificationSender: UpdatesNotificationSender, private val networkStatusChecker: NetworkStatusChecker, + private val appUpdateCompletionHandler: AppUpdateCompletionHandler, ) { @Inject lateinit var downloadManager: DownloadManagerUtils @@ -83,7 +77,6 @@ class AppInstallProcessor @Inject constructor( companion object { private const val TAG = "AppInstallProcessor" - private const val DATE_FORMAT = "dd/MM/yyyy-HH:mm" } /** @@ -369,50 +362,7 @@ class AppInstallProcessor @Inject constructor( private suspend fun checkUpdateWork( appInstall: AppInstall? ) { - if (isItUpdateWork) { - appInstall?.let { - val packageStatus = - appInstallComponents.appManagerWrapper.getFusedDownloadPackageStatus(appInstall) - - if (packageStatus == Status.INSTALLED) { - updatesTracker.addSuccessfullyUpdatedApp(it) - } - - if (isUpdateCompleted()) { // show notification for ended update - showNotificationOnUpdateEnded() - updatesTracker.clearSuccessfullyUpdatedApps() - } - } - } - } - - private suspend fun isUpdateCompleted(): Boolean { - val downloadListWithoutAnyIssue = - appInstallComponents.appInstallRepository.getDownloadList().filter { - !listOf( - Status.INSTALLATION_ISSUE, - Status.PURCHASE_NEEDED - ).contains(it.status) - } - - return updatesTracker.hasSuccessfulUpdatedApps() && downloadListWithoutAnyIssue.isEmpty() - } - - private suspend fun showNotificationOnUpdateEnded() { - val locale = playStoreAuthStore.awaitAuthData()?.locale ?: java.util.Locale.getDefault() - val date = Date().getFormattedString(DATE_FORMAT, locale) - val numberOfUpdatedApps = - NumberFormat.getNumberInstance(locale).format(updatesTracker.successfulUpdatedAppsCount()) - .toString() - - updatesNotificationSender.showNotification( - context.getString(R.string.update), - context.getString( - R.string.message_last_update_triggered, - numberOfUpdatedApps, - date - ) - ) + appUpdateCompletionHandler.onInstallFinished(appInstall, isItUpdateWork) } private suspend fun startAppInstallationProcess(appInstall: AppInstall) { diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppUpdateCompletionHandler.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppUpdateCompletionHandler.kt new file mode 100644 index 000000000..b68636dc6 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppUpdateCompletionHandler.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.data.install.workmanager + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.apps.R +import foundation.e.apps.data.install.AppInstallRepository +import foundation.e.apps.data.install.AppManagerWrapper +import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.install.wrapper.UpdatesNotificationSender +import foundation.e.apps.data.install.wrapper.UpdatesTracker +import foundation.e.apps.data.preference.PlayStoreAuthStore +import foundation.e.apps.data.utils.getFormattedString +import foundation.e.apps.domain.model.install.Status +import java.text.NumberFormat +import java.util.Date +import java.util.Locale +import javax.inject.Inject + +class AppUpdateCompletionHandler @Inject constructor( + @ApplicationContext private val context: Context, + private val appInstallRepository: AppInstallRepository, + private val appManagerWrapper: AppManagerWrapper, + private val playStoreAuthStore: PlayStoreAuthStore, + private val updatesTracker: UpdatesTracker, + private val updatesNotificationSender: UpdatesNotificationSender, +) { + companion object { + private const val DATE_FORMAT = "dd/MM/yyyy-HH:mm" + } + + suspend fun onInstallFinished(appInstall: AppInstall?, isUpdateWork: Boolean) { + if (!isUpdateWork) { + return + } + + appInstall?.let { + val packageStatus = appManagerWrapper.getFusedDownloadPackageStatus(appInstall) + + if (packageStatus == Status.INSTALLED) { + updatesTracker.addSuccessfullyUpdatedApp(it) + } + + if (isUpdateCompleted()) { + showNotificationOnUpdateEnded() + updatesTracker.clearSuccessfullyUpdatedApps() + } + } + } + + private suspend fun isUpdateCompleted(): Boolean { + val downloadListWithoutAnyIssue = appInstallRepository.getDownloadList().filter { + !listOf(Status.INSTALLATION_ISSUE, Status.PURCHASE_NEEDED).contains(it.status) + } + + return updatesTracker.hasSuccessfulUpdatedApps() && downloadListWithoutAnyIssue.isEmpty() + } + + private suspend fun showNotificationOnUpdateEnded() { + val locale = playStoreAuthStore.awaitAuthData()?.locale ?: Locale.getDefault() + val date = Date().getFormattedString(DATE_FORMAT, locale) + val numberOfUpdatedApps = + NumberFormat.getNumberInstance(locale) + .format(updatesTracker.successfulUpdatedAppsCount()) + .toString() + + updatesNotificationSender.showNotification( + context.getString(R.string.update), + context.getString( + R.string.message_last_update_triggered, + numberOfUpdatedApps, + date + ) + ) + } +} diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt index ec07912b7..d073216a6 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt @@ -40,6 +40,7 @@ import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager import foundation.e.apps.data.install.workmanager.AppInstallProcessor +import foundation.e.apps.data.install.workmanager.AppUpdateCompletionHandler import foundation.e.apps.data.install.workmanager.InstallWorkManager import foundation.e.apps.data.install.wrapper.AppEventDispatcher import foundation.e.apps.data.install.wrapper.NetworkStatusChecker @@ -121,6 +122,7 @@ class AppInstallProcessorTest { private lateinit var updatesTracker: UpdatesTracker private lateinit var updatesNotificationSender: UpdatesNotificationSender private lateinit var networkStatusChecker: NetworkStatusChecker + private lateinit var appUpdateCompletionHandler: AppUpdateCompletionHandler private var isInstallWorkManagerMocked = false @@ -145,6 +147,14 @@ class AppInstallProcessorTest { FakeAppManagerWrapper(fakeFusedDownloadDAO, context, fakeFusedManager, fakeFDroidRepository) val appInstallComponents = AppInstallComponents(appInstallRepository, fakeFusedManagerRepository) + appUpdateCompletionHandler = AppUpdateCompletionHandler( + context, + appInstallRepository, + fakeFusedManagerRepository, + playStoreAuthStore, + updatesTracker, + updatesNotificationSender + ) appInstallProcessor = AppInstallProcessor( context, @@ -157,9 +167,8 @@ class AppInstallProcessorTest { appEventDispatcher, storageSpaceChecker, parentalControlAuthGateway, - updatesTracker, - updatesNotificationSender, - networkStatusChecker + networkStatusChecker, + appUpdateCompletionHandler ) } @@ -810,9 +819,8 @@ class AppInstallProcessorTest { appEventDispatcher, storageSpaceChecker, parentalControlAuthGateway, - updatesTracker, - updatesNotificationSender, - networkStatusChecker + networkStatusChecker, + appUpdateCompletionHandler ) } diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppUpdateCompletionHandlerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppUpdateCompletionHandlerTest.kt new file mode 100644 index 000000000..c27314a7f --- /dev/null +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppUpdateCompletionHandlerTest.kt @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.installProcessor + +import android.content.Context +import com.aurora.gplayapi.data.models.AuthData +import foundation.e.apps.R +import foundation.e.apps.data.install.AppInstallRepository +import foundation.e.apps.data.install.AppManagerWrapper +import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.install.workmanager.AppUpdateCompletionHandler +import foundation.e.apps.data.install.wrapper.UpdatesNotificationSender +import foundation.e.apps.data.install.wrapper.UpdatesTracker +import foundation.e.apps.data.preference.PlayStoreAuthStore +import foundation.e.apps.domain.model.install.Status +import foundation.e.apps.util.MainCoroutineRule +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import java.util.Locale + +@OptIn(ExperimentalCoroutinesApi::class) +class AppUpdateCompletionHandlerTest { + + @get:Rule + var mainCoroutineRule = MainCoroutineRule() + + private lateinit var appInstallRepository: AppInstallRepository + private lateinit var appManagerWrapper: AppManagerWrapper + private lateinit var playStoreAuthStore: PlayStoreAuthStore + private lateinit var updatesTracker: UpdatesTracker + private lateinit var updatesNotificationSender: UpdatesNotificationSender + private lateinit var context: Context + private lateinit var handler: AppUpdateCompletionHandler + + @Before + fun setup() { + context = mockk(relaxed = true) + appInstallRepository = AppInstallRepository(FakeAppInstallDAO()) + appManagerWrapper = mockk(relaxed = true) + playStoreAuthStore = mockk(relaxed = true) + updatesTracker = mockk(relaxed = true) + updatesNotificationSender = mockk(relaxed = true) + coEvery { playStoreAuthStore.awaitAuthData() } returns null + handler = AppUpdateCompletionHandler( + context, + appInstallRepository, + appManagerWrapper, + playStoreAuthStore, + updatesTracker, + updatesNotificationSender + ) + } + + @Test + fun onInstallFinished_doesNothingWhenNotUpdateWork() = runTest { + handler.onInstallFinished(AppInstall(id = "123", packageName = "com.example.app"), false) + + verify(exactly = 0) { appManagerWrapper.getFusedDownloadPackageStatus(any()) } + verify(exactly = 0) { updatesTracker.addSuccessfullyUpdatedApp(any()) } + verify(exactly = 0) { updatesNotificationSender.showNotification(any(), any()) } + } + + @Test + fun onInstallFinished_tracksInstalledUpdates() = runTest { + val appInstall = AppInstall(id = "123", packageName = "com.example.app") + every { appManagerWrapper.getFusedDownloadPackageStatus(appInstall) } returns Status.INSTALLED + every { updatesTracker.hasSuccessfulUpdatedApps() } returns false + + handler.onInstallFinished(appInstall, true) + + verify { updatesTracker.addSuccessfullyUpdatedApp(appInstall) } + verify(exactly = 0) { updatesNotificationSender.showNotification(any(), any()) } + } + + @Test + fun onInstallFinished_sendsNotificationWhenUpdateBatchCompletes() = runTest { + val appInstall = AppInstall(id = "123", packageName = "com.example.app") + every { appManagerWrapper.getFusedDownloadPackageStatus(appInstall) } returns Status.INSTALLED + every { updatesTracker.hasSuccessfulUpdatedApps() } returns true + every { updatesTracker.successfulUpdatedAppsCount() } returns 2 + stubUpdateNotificationContext() + + handler.onInstallFinished(appInstall, true) + + verify { updatesNotificationSender.showNotification("Update", "Updated message") } + verify { updatesTracker.clearSuccessfullyUpdatedApps() } + } + + @Test + fun onInstallFinished_ignoresIssueAndPurchaseNeededStatusesForCompletion() = runTest { + val appInstall = AppInstall(id = "123", packageName = "com.example.app") + appInstallRepository.addDownload( + AppInstall( + id = "issue", + status = Status.INSTALLATION_ISSUE, + packageName = "com.example.issue" + ) + ) + appInstallRepository.addDownload( + AppInstall( + id = "purchase", + status = Status.PURCHASE_NEEDED, + packageName = "com.example.purchase" + ) + ) + every { appManagerWrapper.getFusedDownloadPackageStatus(appInstall) } returns Status.INSTALLED + every { updatesTracker.hasSuccessfulUpdatedApps() } returns true + every { updatesTracker.successfulUpdatedAppsCount() } returns 1 + stubUpdateNotificationContext() + + handler.onInstallFinished(appInstall, true) + + verify { updatesNotificationSender.showNotification("Update", "Updated message") } + } + + @Test + fun onInstallFinished_clearsTrackedUpdatesAfterNotification() = runTest { + val appInstall = AppInstall(id = "123", packageName = "com.example.app") + every { appManagerWrapper.getFusedDownloadPackageStatus(appInstall) } returns Status.INSTALLED + every { updatesTracker.hasSuccessfulUpdatedApps() } returns true + every { updatesTracker.successfulUpdatedAppsCount() } returns 1 + stubUpdateNotificationContext() + + handler.onInstallFinished(appInstall, true) + + verify { updatesTracker.clearSuccessfullyUpdatedApps() } + } + + private suspend fun stubUpdateNotificationContext() { + val authData = AuthData(email = "user@example.com", isAnonymous = false).apply { + locale = Locale.US + } + coEvery { playStoreAuthStore.awaitAuthData() } returns authData + every { context.getString(R.string.update) } returns "Update" + every { + context.getString( + R.string.message_last_update_triggered, + any(), + any() + ) + } returns "Updated message" + } +} -- GitLab From c4dedfd1c26587be90d98f26acbad27358f149a6 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Thu, 12 Mar 2026 22:32:57 +0600 Subject: [PATCH 04/43] refactor: remove shared update state from AppInstallProcessor Pass the update-work flag through the install flow explicitly so the async worker path no longer depends on mutable processor state between callbacks. --- .../workmanager/AppInstallProcessor.kt | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 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 3457354ca..4e45b84bc 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 @@ -73,8 +73,6 @@ class AppInstallProcessor @Inject constructor( @Inject lateinit var appManager: AppManager - private var isItUpdateWork = false - companion object { private const val TAG = "AppInstallProcessor" } @@ -303,7 +301,7 @@ class AppInstallProcessor @Inject constructor( appInstall?.let { checkDownloadingState(appInstall) - this.isItUpdateWork = + val isUpdateWork = isItUpdateWork && appInstallComponents.appManagerWrapper.isFusedDownloadInstalled( appInstall ) @@ -329,7 +327,7 @@ class AppInstallProcessor @Inject constructor( runInForeground.invoke(it.name) - startAppInstallationProcess(it) + startAppInstallationProcess(it, isUpdateWork) } } catch (e: Exception) { Timber.e( @@ -359,15 +357,13 @@ class AppInstallProcessor @Inject constructor( appInstall ) || appInstall.status == Status.INSTALLING) - private suspend fun checkUpdateWork( - appInstall: AppInstall? - ) { - appUpdateCompletionHandler.onInstallFinished(appInstall, isItUpdateWork) + private suspend fun checkUpdateWork(appInstall: AppInstall?, isUpdateWork: Boolean) { + appUpdateCompletionHandler.onInstallFinished(appInstall, isUpdateWork) } - private suspend fun startAppInstallationProcess(appInstall: AppInstall) { + private suspend fun startAppInstallationProcess(appInstall: AppInstall, isUpdateWork: Boolean) { if (appInstall.isAwaiting()) { - appInstallComponents.appManagerWrapper.downloadApp(appInstall, isItUpdateWork) + appInstallComponents.appManagerWrapper.downloadApp(appInstall, isUpdateWork) Timber.i("===> doWork: Download started ${appInstall.name} ${appInstall.status}") } @@ -377,7 +373,7 @@ class AppInstallProcessor @Inject constructor( isInstallRunning(it) } .collect { latestFusedDownload -> - handleFusedDownload(latestFusedDownload, appInstall) + handleFusedDownload(latestFusedDownload, appInstall, isUpdateWork) } } @@ -390,35 +386,37 @@ class AppInstallProcessor @Inject constructor( */ private suspend fun handleFusedDownload( latestAppInstall: AppInstall?, - appInstall: AppInstall + appInstall: AppInstall, + isUpdateWork: Boolean ) { if (latestAppInstall == null) { Timber.d("===> download null: finish installation") - finishInstallation(appInstall) + finishInstallation(appInstall, isUpdateWork) return } - handleFusedDownloadStatusCheckingException(latestAppInstall) + handleFusedDownloadStatusCheckingException(latestAppInstall, isUpdateWork) } private fun isInstallRunning(it: AppInstall?) = it != null && it.status != Status.INSTALLATION_ISSUE private suspend fun handleFusedDownloadStatusCheckingException( - download: AppInstall + download: AppInstall, + isUpdateWork: Boolean ) { try { - handleFusedDownloadStatus(download) + handleFusedDownloadStatus(download, isUpdateWork) } catch (e: Exception) { val message = "Handling install status is failed for ${download.packageName} exception: ${e.localizedMessage}" Timber.e(e, message) appInstallComponents.appManagerWrapper.installationIssue(download) - finishInstallation(download) + finishInstallation(download, isUpdateWork) } } - private suspend fun handleFusedDownloadStatus(appInstall: AppInstall) { + private suspend fun handleFusedDownloadStatus(appInstall: AppInstall, isUpdateWork: Boolean) { when (appInstall.status) { Status.AWAITING, Status.DOWNLOADING -> { } @@ -436,7 +434,7 @@ class AppInstallProcessor @Inject constructor( Status.INSTALLED, Status.INSTALLATION_ISSUE -> { Timber.i("===> doWork: Installed/Failed: ${appInstall.name} ${appInstall.status}") - finishInstallation(appInstall) + finishInstallation(appInstall, isUpdateWork) } else -> { @@ -444,12 +442,12 @@ class AppInstallProcessor @Inject constructor( TAG, "===> ${appInstall.name} is in wrong state ${appInstall.status}" ) - finishInstallation(appInstall) + finishInstallation(appInstall, isUpdateWork) } } } - private suspend fun finishInstallation(appInstall: AppInstall) { - checkUpdateWork(appInstall) + private suspend fun finishInstallation(appInstall: AppInstall, isUpdateWork: Boolean) { + checkUpdateWork(appInstall, isUpdateWork) } } -- GitLab From 25525db489d0b2577b0a35e51da6f04d5eafb0c9 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Thu, 12 Mar 2026 23:25:29 +0600 Subject: [PATCH 05/43] refactor: extract AppInstallProcessor age validation flow into separate class Move the age-limit dialog and parental-auth checks behind AppInstallAgeLimitGate so enqueue validation keeps the current order while the restriction flow gains direct unit coverage. --- .../workmanager/AppInstallAgeLimitGate.kt | 70 +++++++++ .../workmanager/AppInstallProcessor.kt | 40 +---- .../AppInstallAgeLimitGateTest.kt | 140 ++++++++++++++++++ .../AppInstallProcessorTest.kt | 32 ++-- .../FakeAppEventDispatcher.kt | 35 +++++ 5 files changed, 265 insertions(+), 52 deletions(-) create mode 100644 app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallAgeLimitGate.kt create mode 100644 app/src/test/java/foundation/e/apps/installProcessor/AppInstallAgeLimitGateTest.kt create mode 100644 app/src/test/java/foundation/e/apps/installProcessor/FakeAppEventDispatcher.kt diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallAgeLimitGate.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallAgeLimitGate.kt new file mode 100644 index 000000000..56cabf5fe --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallAgeLimitGate.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.data.install.workmanager + +import foundation.e.apps.R +import foundation.e.apps.data.event.AppEvent +import foundation.e.apps.data.install.AppManagerWrapper +import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.install.wrapper.AppEventDispatcher +import foundation.e.apps.data.install.wrapper.ParentalControlAuthGateway +import foundation.e.apps.domain.ValidateAppAgeLimitUseCase +import foundation.e.apps.domain.model.ContentRatingValidity +import kotlinx.coroutines.CompletableDeferred +import javax.inject.Inject + +@Suppress("ReturnCount") // FIXME: Remove suppression and fix detekt +class AppInstallAgeLimitGate @Inject constructor( + private val validateAppAgeLimitUseCase: ValidateAppAgeLimitUseCase, + private val appManagerWrapper: AppManagerWrapper, + private val appEventDispatcher: AppEventDispatcher, + private val parentalControlAuthGateway: ParentalControlAuthGateway, +) { + suspend fun allow(appInstall: AppInstall): Boolean { + val ageLimitValidationResult = validateAppAgeLimitUseCase(appInstall) + if (ageLimitValidationResult.data?.isValid == true) { + return true + } + + if (ageLimitValidationResult.isSuccess()) { + awaitInvokeAgeLimitEvent(appInstall.name) + if (ageLimitValidationResult.data?.requestPin == true) { + val isAuthenticated = parentalControlAuthGateway.awaitAuthentication() + if (isAuthenticated) { + ageLimitValidationResult.setData(ContentRatingValidity(true)) + } + } + } else { + appEventDispatcher.dispatch(AppEvent.ErrorMessageDialogEvent(R.string.data_load_error_desc)) + } + + if (ageLimitValidationResult.data?.isValid == true) { + return true + } + + appManagerWrapper.cancelDownload(appInstall) + return false + } + + private suspend fun awaitInvokeAgeLimitEvent(type: String) { + val deferred = CompletableDeferred() + appEventDispatcher.dispatch(AppEvent.AgeLimitRestrictionEvent(type, deferred)) + deferred.await() + } +} 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 4e45b84bc..24fb08667 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 @@ -37,16 +37,12 @@ import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager import foundation.e.apps.data.install.wrapper.AppEventDispatcher import foundation.e.apps.data.install.wrapper.NetworkStatusChecker -import foundation.e.apps.data.install.wrapper.ParentalControlAuthGateway import foundation.e.apps.data.install.wrapper.StorageSpaceChecker import foundation.e.apps.data.playstore.utils.GplayHttpRequestException import foundation.e.apps.data.preference.PlayStoreAuthStore -import foundation.e.apps.domain.ValidateAppAgeLimitUseCase -import foundation.e.apps.domain.model.ContentRatingValidity import foundation.e.apps.domain.model.User import foundation.e.apps.domain.model.install.Status import foundation.e.apps.domain.preferences.SessionRepository -import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.flow.transformWhile import timber.log.Timber @@ -57,14 +53,13 @@ class AppInstallProcessor @Inject constructor( @ApplicationContext private val context: Context, private val appInstallComponents: AppInstallComponents, private val applicationRepository: ApplicationRepository, - private val validateAppAgeLimitUseCase: ValidateAppAgeLimitUseCase, private val sessionRepository: SessionRepository, private val playStoreAuthStore: PlayStoreAuthStore, private val storageNotificationManager: StorageNotificationManager, private val appEventDispatcher: AppEventDispatcher, private val storageSpaceChecker: StorageSpaceChecker, - private val parentalControlAuthGateway: ParentalControlAuthGateway, private val networkStatusChecker: NetworkStatusChecker, + private val appInstallAgeLimitGate: AppInstallAgeLimitGate, private val appUpdateCompletionHandler: AppUpdateCompletionHandler, ) { @Inject @@ -170,7 +165,7 @@ class AppInstallProcessor @Inject constructor( return false } - if (!validateAgeLimit(appInstall)) { + if (!appInstallAgeLimitGate.allow(appInstall)) { return false } @@ -191,37 +186,6 @@ class AppInstallProcessor @Inject constructor( return true } - private suspend fun validateAgeLimit(appInstall: AppInstall): Boolean { - val ageLimitValidationResult = validateAppAgeLimitUseCase(appInstall) - if (ageLimitValidationResult.data?.isValid == true) { - return true - } - if (ageLimitValidationResult.isSuccess()) { - awaitInvokeAgeLimitEvent(appInstall.name) - if (ageLimitValidationResult.data?.requestPin == true) { - val isAuthenticated = parentalControlAuthGateway.awaitAuthentication() - if (isAuthenticated) { - ageLimitValidationResult.setData(ContentRatingValidity(true)) - } - } - } else { - appEventDispatcher.dispatch(AppEvent.ErrorMessageDialogEvent(R.string.data_load_error_desc)) - } - - var ageIsValid = true - if (ageLimitValidationResult.data?.isValid != true) { - appInstallComponents.appManagerWrapper.cancelDownload(appInstall) - ageIsValid = false - } - return ageIsValid - } - - suspend fun awaitInvokeAgeLimitEvent(type: String) { - val deferred = CompletableDeferred() - appEventDispatcher.dispatch(AppEvent.AgeLimitRestrictionEvent(type, deferred)) - deferred.await() // await closing dialog box - } - // returns TRUE if updating urls is successful, otherwise false. private suspend fun updateDownloadUrls(appInstall: AppInstall): Boolean { try { diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallAgeLimitGateTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallAgeLimitGateTest.kt new file mode 100644 index 000000000..19de4f0f8 --- /dev/null +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallAgeLimitGateTest.kt @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.installProcessor + +import foundation.e.apps.R +import foundation.e.apps.data.ResultSupreme +import foundation.e.apps.data.enums.ResultStatus +import foundation.e.apps.data.event.AppEvent +import foundation.e.apps.data.install.AppManagerWrapper +import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.install.workmanager.AppInstallAgeLimitGate +import foundation.e.apps.data.install.wrapper.ParentalControlAuthGateway +import foundation.e.apps.domain.ValidateAppAgeLimitUseCase +import foundation.e.apps.domain.model.ContentRatingValidity +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito + +@OptIn(ExperimentalCoroutinesApi::class) +class AppInstallAgeLimitGateTest { + private lateinit var validateAppAgeLimitUseCase: ValidateAppAgeLimitUseCase + private lateinit var appManagerWrapper: AppManagerWrapper + private lateinit var parentalControlAuthGateway: ParentalControlAuthGateway + private lateinit var appEventDispatcher: FakeAppEventDispatcher + private lateinit var gate: AppInstallAgeLimitGate + + @Before + fun setup() { + validateAppAgeLimitUseCase = Mockito.mock(ValidateAppAgeLimitUseCase::class.java) + appManagerWrapper = mockk(relaxed = true) + parentalControlAuthGateway = mockk(relaxed = true) + appEventDispatcher = FakeAppEventDispatcher(autoCompleteDeferred = true) + gate = AppInstallAgeLimitGate( + validateAppAgeLimitUseCase, + appManagerWrapper, + appEventDispatcher, + parentalControlAuthGateway + ) + } + + @Test + fun allow_returnsTrueWhenAgeRatingIsValid() = runTest { + val appInstall = AppInstall(id = "123", packageName = "com.example.app") + Mockito.`when`(validateAppAgeLimitUseCase(appInstall)) + .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) + + val result = gate.allow(appInstall) + + assertTrue(result) + coVerify(exactly = 0) { appManagerWrapper.cancelDownload(any()) } + } + + @Test + fun allow_returnsFalseAndCancelsWhenAgeRatingInvalidWithoutPin() = runTest { + val appInstall = AppInstall(id = "123", name = "App", packageName = "com.example.app") + Mockito.`when`(validateAppAgeLimitUseCase(appInstall)) + .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(false))) + + val result = gate.allow(appInstall) + + assertFalse(result) + assertTrue(appEventDispatcher.events.any { it is AppEvent.AgeLimitRestrictionEvent }) + coVerify { appManagerWrapper.cancelDownload(appInstall) } + } + + @Test + fun allow_returnsTrueWhenPinIsRequiredAndAuthenticationSucceeds() = runTest { + val appInstall = AppInstall(id = "123", name = "App", packageName = "com.example.app") + Mockito.`when`(validateAppAgeLimitUseCase(appInstall)) + .thenReturn( + ResultSupreme.create( + ResultStatus.OK, + ContentRatingValidity(false, requestPin = true) + ) + ) + coEvery { parentalControlAuthGateway.awaitAuthentication() } returns true + + val result = gate.allow(appInstall) + + assertTrue(result) + assertTrue(appEventDispatcher.events.any { it is AppEvent.AgeLimitRestrictionEvent }) + coVerify(exactly = 0) { appManagerWrapper.cancelDownload(any()) } + } + + @Test + fun allow_returnsFalseWhenPinIsRequiredAndAuthenticationFails() = runTest { + val appInstall = AppInstall(id = "123", name = "App", packageName = "com.example.app") + Mockito.`when`(validateAppAgeLimitUseCase(appInstall)) + .thenReturn( + ResultSupreme.create( + ResultStatus.OK, + ContentRatingValidity(false, requestPin = true) + ) + ) + coEvery { parentalControlAuthGateway.awaitAuthentication() } returns false + + val result = gate.allow(appInstall) + + assertFalse(result) + coVerify { appManagerWrapper.cancelDownload(appInstall) } + } + + @Test + fun allow_dispatchesErrorDialogWhenValidationFails() = runTest { + val appInstall = AppInstall(id = "123", packageName = "com.example.app") + Mockito.`when`(validateAppAgeLimitUseCase(appInstall)) + .thenReturn(ResultSupreme.create(ResultStatus.UNKNOWN, ContentRatingValidity(false))) + + val result = gate.allow(appInstall) + + assertFalse(result) + val errorDialogEvent = appEventDispatcher.events.last() as AppEvent.ErrorMessageDialogEvent + assertEquals(R.string.data_load_error_desc, errorDialogEvent.data) + coVerify { appManagerWrapper.cancelDownload(appInstall) } + } +} diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt index d073216a6..2b947f98e 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt @@ -39,6 +39,7 @@ import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager +import foundation.e.apps.data.install.workmanager.AppInstallAgeLimitGate import foundation.e.apps.data.install.workmanager.AppInstallProcessor import foundation.e.apps.data.install.workmanager.AppUpdateCompletionHandler import foundation.e.apps.data.install.workmanager.InstallWorkManager @@ -116,12 +117,13 @@ class AppInstallProcessorTest { @Mock private lateinit var storageNotificationManager: StorageNotificationManager - private lateinit var appEventDispatcher: RecordingAppEventDispatcher + private lateinit var appEventDispatcher: FakeAppEventDispatcher private lateinit var storageSpaceChecker: StorageSpaceChecker private lateinit var parentalControlAuthGateway: ParentalControlAuthGateway private lateinit var updatesTracker: UpdatesTracker private lateinit var updatesNotificationSender: UpdatesNotificationSender private lateinit var networkStatusChecker: NetworkStatusChecker + private lateinit var appInstallAgeLimitGate: AppInstallAgeLimitGate private lateinit var appUpdateCompletionHandler: AppUpdateCompletionHandler private var isInstallWorkManagerMocked = false @@ -135,7 +137,7 @@ class AppInstallProcessorTest { applicationRepository = mockk(relaxed = true) coEvery { sessionRepository.awaitUser() } returns User.NO_GOOGLE coEvery { playStoreAuthStore.awaitAuthData() } returns null - appEventDispatcher = RecordingAppEventDispatcher() + appEventDispatcher = FakeAppEventDispatcher() storageSpaceChecker = mockk(relaxed = true) parentalControlAuthGateway = mockk(relaxed = true) updatesTracker = mockk(relaxed = true) @@ -147,6 +149,12 @@ class AppInstallProcessorTest { FakeAppManagerWrapper(fakeFusedDownloadDAO, context, fakeFusedManager, fakeFDroidRepository) val appInstallComponents = AppInstallComponents(appInstallRepository, fakeFusedManagerRepository) + appInstallAgeLimitGate = AppInstallAgeLimitGate( + validateAppAgeRatingUseCase, + fakeFusedManagerRepository, + appEventDispatcher, + parentalControlAuthGateway + ) appUpdateCompletionHandler = AppUpdateCompletionHandler( context, appInstallRepository, @@ -160,14 +168,13 @@ class AppInstallProcessorTest { context, appInstallComponents, applicationRepository, - validateAppAgeRatingUseCase, sessionRepository, playStoreAuthStore, storageNotificationManager, appEventDispatcher, storageSpaceChecker, - parentalControlAuthGateway, networkStatusChecker, + appInstallAgeLimitGate, appUpdateCompletionHandler ) } @@ -808,18 +815,23 @@ class AppInstallProcessorTest { ): AppInstallProcessor { val appInstallRepository = AppInstallRepository(FakeAppInstallDAO()) val appInstallComponents = AppInstallComponents(appInstallRepository, appManagerWrapper) + val ageLimitGate = AppInstallAgeLimitGate( + validateAppAgeRatingUseCase, + appManagerWrapper, + appEventDispatcher, + parentalControlAuthGateway + ) return AppInstallProcessor( context, appInstallComponents, applicationRepository, - validateAppAgeRatingUseCase, sessionRepository, playStoreAuthStore, storageNotificationManager, appEventDispatcher, storageSpaceChecker, - parentalControlAuthGateway, networkStatusChecker, + ageLimitGate, appUpdateCompletionHandler ) } @@ -889,11 +901,3 @@ class AppInstallProcessorTest { } } - -private class RecordingAppEventDispatcher : AppEventDispatcher { - val events = mutableListOf() - - override suspend fun dispatch(event: AppEvent) { - events.add(event) - } -} diff --git a/app/src/test/java/foundation/e/apps/installProcessor/FakeAppEventDispatcher.kt b/app/src/test/java/foundation/e/apps/installProcessor/FakeAppEventDispatcher.kt new file mode 100644 index 000000000..b9ff56e85 --- /dev/null +++ b/app/src/test/java/foundation/e/apps/installProcessor/FakeAppEventDispatcher.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.installProcessor + +import foundation.e.apps.data.event.AppEvent +import foundation.e.apps.data.install.wrapper.AppEventDispatcher + +internal class FakeAppEventDispatcher( + private val autoCompleteDeferred: Boolean = false +) : AppEventDispatcher { + val events = mutableListOf() + + override suspend fun dispatch(event: AppEvent) { + events.add(event) + if (autoCompleteDeferred && event is AppEvent.AgeLimitRestrictionEvent) { + event.onClose?.complete(Unit) + } + } +} -- GitLab From 646ad80481b6d6653366d6dd148f2549df381d00 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Thu, 12 Mar 2026 23:40:59 +0600 Subject: [PATCH 06/43] refactor: move the install-state machine from AppInstallProcessor to AppInstallWorkRunner Move the install-state machine and flow handling into a dedicated runner so worker behavior can be tested directly while the processor keeps the same external contract. --- .../workmanager/AppInstallProcessor.kt | 170 +------------- .../workmanager/AppInstallWorkRunner.kt | 176 +++++++++++++++ .../AppInstallProcessorTest.kt | 20 +- .../AppInstallWorkRunnerTest.kt | 209 ++++++++++++++++++ 4 files changed, 405 insertions(+), 170 deletions(-) create mode 100644 app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallWorkRunner.kt create mode 100644 app/src/test/java/foundation/e/apps/installProcessor/AppInstallWorkRunnerTest.kt 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 24fb08667..797dcf951 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 @@ -32,7 +32,6 @@ import foundation.e.apps.data.enums.Type import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppInstallComponents import foundation.e.apps.data.install.AppManager -import foundation.e.apps.data.install.download.DownloadManagerUtils import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager import foundation.e.apps.data.install.wrapper.AppEventDispatcher @@ -44,7 +43,6 @@ import foundation.e.apps.domain.model.User import foundation.e.apps.domain.model.install.Status import foundation.e.apps.domain.preferences.SessionRepository import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.flow.transformWhile import timber.log.Timber import javax.inject.Inject @@ -60,18 +58,11 @@ class AppInstallProcessor @Inject constructor( private val storageSpaceChecker: StorageSpaceChecker, private val networkStatusChecker: NetworkStatusChecker, private val appInstallAgeLimitGate: AppInstallAgeLimitGate, - private val appUpdateCompletionHandler: AppUpdateCompletionHandler, + private val appInstallWorkRunner: AppInstallWorkRunner, ) { - @Inject - lateinit var downloadManager: DownloadManagerUtils - @Inject lateinit var appManager: AppManager - companion object { - private const val TAG = "AppInstallProcessor" - } - /** * creates [AppInstall] from [Application] and enqueues into WorkManager to run install process. * @param application represents the app info which will be installed @@ -255,163 +246,6 @@ class AppInstallProcessor @Inject constructor( isItUpdateWork: Boolean, runInForeground: (suspend (String) -> Unit) ): 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) - - val isUpdateWork = - isItUpdateWork && appInstallComponents.appManagerWrapper.isFusedDownloadInstalled( - appInstall - ) - - if (!appInstall.isAppInstalling()) { - Timber.d("!!! returned") - return@let - } - - if (!appInstallComponents.appManagerWrapper.validateFusedDownload(appInstall)) { - appInstallComponents.appManagerWrapper.installationIssue(it) - Timber.d("!!! installationIssue") - return@let - } - - if (areFilesDownloadedButNotInstalled(appInstall)) { - Timber.i("===> Downloaded But not installed ${appInstall.name}") - appInstallComponents.appManagerWrapper.updateDownloadStatus( - appInstall, - Status.INSTALLING - ) - } - - runInForeground.invoke(it.name) - - startAppInstallationProcess(it, isUpdateWork) - } - } catch (e: Exception) { - Timber.e( - e, - "Install worker is failed for ${appInstall?.packageName} exception: ${e.localizedMessage}" - ) - appInstall?.let { - appInstallComponents.appManagerWrapper.cancelDownload(appInstall) - } - } - - Timber.i("doWork: RESULT SUCCESS: ${appInstall?.name}") - return Result.success(ResultStatus.OK) - } - - @OptIn(DelicateCoroutinesApi::class) - private fun checkDownloadingState(appInstall: AppInstall) { - if (appInstall.status == Status.DOWNLOADING) { - appInstall.downloadIdMap.keys.forEach { downloadId -> - downloadManager.updateDownloadStatus(downloadId) - } - } - } - - private fun areFilesDownloadedButNotInstalled(appInstall: AppInstall) = - appInstall.areFilesDownloaded() && (!appInstallComponents.appManagerWrapper.isFusedDownloadInstalled( - appInstall - ) || appInstall.status == Status.INSTALLING) - - private suspend fun checkUpdateWork(appInstall: AppInstall?, isUpdateWork: Boolean) { - appUpdateCompletionHandler.onInstallFinished(appInstall, isUpdateWork) - } - - private suspend fun startAppInstallationProcess(appInstall: AppInstall, isUpdateWork: Boolean) { - if (appInstall.isAwaiting()) { - appInstallComponents.appManagerWrapper.downloadApp(appInstall, isUpdateWork) - Timber.i("===> doWork: Download started ${appInstall.name} ${appInstall.status}") - } - - appInstallComponents.appInstallRepository.getDownloadFlowById(appInstall.id) - .transformWhile { - emit(it) - isInstallRunning(it) - } - .collect { latestFusedDownload -> - handleFusedDownload(latestFusedDownload, appInstall, isUpdateWork) - } - } - - /** - * Takes actions depending on the status of [AppInstall] - * - * @param latestAppInstall comes from Room database when [Status] is updated - * @param appInstall is the original object when install process isn't started. It's used when [latestAppInstall] - * becomes null, After installation is completed. - */ - private suspend fun handleFusedDownload( - latestAppInstall: AppInstall?, - appInstall: AppInstall, - isUpdateWork: Boolean - ) { - if (latestAppInstall == null) { - Timber.d("===> download null: finish installation") - finishInstallation(appInstall, isUpdateWork) - return - } - - handleFusedDownloadStatusCheckingException(latestAppInstall, isUpdateWork) - } - - private fun isInstallRunning(it: AppInstall?) = - it != null && it.status != Status.INSTALLATION_ISSUE - - private suspend fun handleFusedDownloadStatusCheckingException( - download: AppInstall, - isUpdateWork: Boolean - ) { - try { - handleFusedDownloadStatus(download, isUpdateWork) - } catch (e: Exception) { - val message = - "Handling install status is failed for ${download.packageName} exception: ${e.localizedMessage}" - Timber.e(e, message) - appInstallComponents.appManagerWrapper.installationIssue(download) - finishInstallation(download, isUpdateWork) - } - } - - private suspend fun handleFusedDownloadStatus(appInstall: AppInstall, isUpdateWork: Boolean) { - when (appInstall.status) { - Status.AWAITING, Status.DOWNLOADING -> { - } - - Status.DOWNLOADED -> { - appInstallComponents.appManagerWrapper.updateDownloadStatus( - appInstall, - Status.INSTALLING - ) - } - - Status.INSTALLING -> { - Timber.i("===> doWork: Installing ${appInstall.name} ${appInstall.status}") - } - - Status.INSTALLED, Status.INSTALLATION_ISSUE -> { - Timber.i("===> doWork: Installed/Failed: ${appInstall.name} ${appInstall.status}") - finishInstallation(appInstall, isUpdateWork) - } - - else -> { - Timber.wtf( - TAG, - "===> ${appInstall.name} is in wrong state ${appInstall.status}" - ) - finishInstallation(appInstall, isUpdateWork) - } - } - } - - private suspend fun finishInstallation(appInstall: AppInstall, isUpdateWork: Boolean) { - checkUpdateWork(appInstall, isUpdateWork) + return appInstallWorkRunner.processInstall(fusedDownloadId, isItUpdateWork, runInForeground) } } diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallWorkRunner.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallWorkRunner.kt new file mode 100644 index 000000000..ac9792e65 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallWorkRunner.kt @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.data.install.workmanager + +import foundation.e.apps.data.enums.ResultStatus +import foundation.e.apps.data.install.AppInstallRepository +import foundation.e.apps.data.install.AppManagerWrapper +import foundation.e.apps.data.install.download.DownloadManagerUtils +import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.domain.model.install.Status +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.flow.transformWhile +import timber.log.Timber +import javax.inject.Inject + +@Suppress("TooGenericExceptionCaught") // FIXME: Remove suppression and fix detekt +class AppInstallWorkRunner @Inject constructor( + private val appInstallRepository: AppInstallRepository, + private val appManagerWrapper: AppManagerWrapper, + private val downloadManager: DownloadManagerUtils, + private val appUpdateCompletionHandler: AppUpdateCompletionHandler, +) { + @OptIn(DelicateCoroutinesApi::class) + suspend fun processInstall( + fusedDownloadId: String, + isItUpdateWork: Boolean, + runInForeground: suspend (String) -> Unit + ): Result { + var appInstall: AppInstall? = null + try { + Timber.d("Fused download name $fusedDownloadId") + + appInstall = appInstallRepository.getDownloadById(fusedDownloadId) + Timber.i(">>> doWork started for Fused download name ${appInstall?.name} $fusedDownloadId") + + appInstall?.let { + checkDownloadingState(appInstall) + + val isUpdateWork = + isItUpdateWork && appManagerWrapper.isFusedDownloadInstalled(appInstall) + + if (!appInstall.isAppInstalling()) { + Timber.d("!!! returned") + return@let + } + + if (!appManagerWrapper.validateFusedDownload(appInstall)) { + appManagerWrapper.installationIssue(it) + Timber.d("!!! installationIssue") + return@let + } + + if (areFilesDownloadedButNotInstalled(appInstall)) { + Timber.i("===> Downloaded But not installed ${appInstall.name}") + appManagerWrapper.updateDownloadStatus(appInstall, Status.INSTALLING) + } + + runInForeground.invoke(it.name) + + startAppInstallationProcess(it, isUpdateWork) + } + } catch (e: Exception) { + Timber.e( + e, + "Install worker is failed for ${appInstall?.packageName} exception: ${e.localizedMessage}" + ) + appInstall?.let { + appManagerWrapper.cancelDownload(appInstall) + } + } + + Timber.i("doWork: RESULT SUCCESS: ${appInstall?.name}") + return Result.success(ResultStatus.OK) + } + + @OptIn(DelicateCoroutinesApi::class) + private fun checkDownloadingState(appInstall: AppInstall) { + if (appInstall.status == Status.DOWNLOADING) { + appInstall.downloadIdMap.keys.forEach { downloadId -> + downloadManager.updateDownloadStatus(downloadId) + } + } + } + + private fun areFilesDownloadedButNotInstalled(appInstall: AppInstall): Boolean = appInstall.areFilesDownloaded() && + (!appManagerWrapper.isFusedDownloadInstalled(appInstall) || appInstall.status == Status.INSTALLING) + + private suspend fun startAppInstallationProcess(appInstall: AppInstall, isUpdateWork: Boolean) { + if (appInstall.isAwaiting()) { + appManagerWrapper.downloadApp(appInstall, isUpdateWork) + Timber.i("===> doWork: Download started ${appInstall.name} ${appInstall.status}") + } + + appInstallRepository.getDownloadFlowById(appInstall.id) + .transformWhile { + emit(it) + isInstallRunning(it) + } + .collect { latestFusedDownload -> + handleFusedDownload(latestFusedDownload, appInstall, isUpdateWork) + } + } + + private suspend fun handleFusedDownload( + latestAppInstall: AppInstall?, + appInstall: AppInstall, + isUpdateWork: Boolean + ) { + if (latestAppInstall == null) { + Timber.d("===> download null: finish installation") + finishInstallation(appInstall, isUpdateWork) + return + } + + handleFusedDownloadStatusCheckingException(latestAppInstall, isUpdateWork) + } + + private fun isInstallRunning(it: AppInstall?) = + it != null && it.status != Status.INSTALLATION_ISSUE + + private suspend fun handleFusedDownloadStatusCheckingException( + download: AppInstall, + isUpdateWork: Boolean + ) { + try { + handleFusedDownloadStatus(download, isUpdateWork) + } catch (e: Exception) { + val message = + "Handling install status is failed for ${download.packageName} exception: ${e.localizedMessage}" + Timber.e(e, message) + appManagerWrapper.installationIssue(download) + finishInstallation(download, isUpdateWork) + } + } + + private suspend fun handleFusedDownloadStatus(appInstall: AppInstall, isUpdateWork: Boolean) { + when (appInstall.status) { + Status.AWAITING, Status.DOWNLOADING -> Unit + Status.DOWNLOADED -> appManagerWrapper.updateDownloadStatus( + appInstall, + Status.INSTALLING + ) + + Status.INSTALLING -> Timber.i("===> doWork: Installing ${appInstall.name} ${appInstall.status}") + Status.INSTALLED, Status.INSTALLATION_ISSUE -> { + Timber.i("===> doWork: Installed/Failed: ${appInstall.name} ${appInstall.status}") + finishInstallation(appInstall, isUpdateWork) + } + + else -> { + Timber.w("===> ${appInstall.name} is in wrong state ${appInstall.status}") + finishInstallation(appInstall, isUpdateWork) + } + } + } + + private suspend fun finishInstallation(appInstall: AppInstall, isUpdateWork: Boolean) { + appUpdateCompletionHandler.onInstallFinished(appInstall, isUpdateWork) + } +} diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt index 2b947f98e..0055f81be 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt @@ -41,6 +41,7 @@ import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager import foundation.e.apps.data.install.workmanager.AppInstallAgeLimitGate import foundation.e.apps.data.install.workmanager.AppInstallProcessor +import foundation.e.apps.data.install.workmanager.AppInstallWorkRunner import foundation.e.apps.data.install.workmanager.AppUpdateCompletionHandler import foundation.e.apps.data.install.workmanager.InstallWorkManager import foundation.e.apps.data.install.wrapper.AppEventDispatcher @@ -125,6 +126,7 @@ class AppInstallProcessorTest { private lateinit var networkStatusChecker: NetworkStatusChecker private lateinit var appInstallAgeLimitGate: AppInstallAgeLimitGate private lateinit var appUpdateCompletionHandler: AppUpdateCompletionHandler + private lateinit var appInstallWorkRunner: AppInstallWorkRunner private var isInstallWorkManagerMocked = false @@ -163,6 +165,14 @@ class AppInstallProcessorTest { updatesTracker, updatesNotificationSender ) + val downloadManager = + mockk(relaxed = true) + appInstallWorkRunner = AppInstallWorkRunner( + appInstallRepository, + fakeFusedManagerRepository, + downloadManager, + appUpdateCompletionHandler + ) appInstallProcessor = AppInstallProcessor( context, @@ -175,7 +185,7 @@ class AppInstallProcessorTest { storageSpaceChecker, networkStatusChecker, appInstallAgeLimitGate, - appUpdateCompletionHandler + appInstallWorkRunner ) } @@ -821,6 +831,12 @@ class AppInstallProcessorTest { appEventDispatcher, parentalControlAuthGateway ) + val workRunner = AppInstallWorkRunner( + appInstallRepository, + appManagerWrapper, + mockk(relaxed = true), + appUpdateCompletionHandler + ) return AppInstallProcessor( context, appInstallComponents, @@ -832,7 +848,7 @@ class AppInstallProcessorTest { storageSpaceChecker, networkStatusChecker, ageLimitGate, - appUpdateCompletionHandler + workRunner ) } diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallWorkRunnerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallWorkRunnerTest.kt new file mode 100644 index 000000000..7ac00a528 --- /dev/null +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallWorkRunnerTest.kt @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.installProcessor + +import android.content.Context +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import foundation.e.apps.data.enums.ResultStatus +import foundation.e.apps.data.fdroid.FDroidRepository +import foundation.e.apps.data.install.AppInstallRepository +import foundation.e.apps.data.install.AppManager +import foundation.e.apps.data.install.download.DownloadManagerUtils +import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.install.workmanager.AppInstallWorkRunner +import foundation.e.apps.data.install.workmanager.AppUpdateCompletionHandler +import foundation.e.apps.domain.model.install.Status +import foundation.e.apps.util.MainCoroutineRule +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +class AppInstallWorkRunnerTest { + @Rule + @JvmField + val instantExecutorRule = InstantTaskExecutorRule() + + @get:Rule + var mainCoroutineRule = MainCoroutineRule() + + private lateinit var fakeFusedDownloadDAO: FakeAppInstallDAO + private lateinit var appInstallRepository: AppInstallRepository + private lateinit var fakeFusedManagerRepository: FakeAppManagerWrapper + private lateinit var downloadManagerUtils: DownloadManagerUtils + private lateinit var appUpdateCompletionHandler: AppUpdateCompletionHandler + private lateinit var workRunner: AppInstallWorkRunner + private lateinit var context: Context + + @Mock + private lateinit var fakeFusedManager: AppManager + + @Mock + private lateinit var fakeFDroidRepository: FDroidRepository + + @Before + fun setup() { + MockitoAnnotations.openMocks(this) + context = mockk(relaxed = true) + fakeFusedDownloadDAO = FakeAppInstallDAO() + appInstallRepository = AppInstallRepository(fakeFusedDownloadDAO) + fakeFusedManagerRepository = + FakeAppManagerWrapper(fakeFusedDownloadDAO, context, fakeFusedManager, fakeFDroidRepository) + downloadManagerUtils = mockk(relaxed = true) + appUpdateCompletionHandler = mockk(relaxed = true) + workRunner = AppInstallWorkRunner( + appInstallRepository, + fakeFusedManagerRepository, + downloadManagerUtils, + appUpdateCompletionHandler + ) + } + + @Test + fun processInstall_completesNormalFlow() = runTest { + val fusedDownload = initTest() + + val finalFusedDownload = runProcessInstall(fusedDownload) + + assertTrue(finalFusedDownload == null) + } + + @Test + fun processInstall_keepsBlockedDownloadUntouched() = runTest { + val fusedDownload = initTest() + fusedDownload.status = Status.BLOCKED + + val finalFusedDownload = runProcessInstall(fusedDownload) + + assertEquals(Status.BLOCKED, finalFusedDownload?.status) + } + + @Test + fun processInstall_marksDownloadedFilesAsInstalling() = runTest { + val fusedDownload = initTest() + fusedDownload.downloadIdMap = mutableMapOf(Pair(231, true)) + + val finalFusedDownload = runProcessInstall(fusedDownload) + + assertTrue(finalFusedDownload == null) + } + + @Test + fun processInstall_reportsInvalidPackageAsInstallationIssue() = runTest { + val fusedDownload = initTest(packageName = "") + fusedDownload.downloadIdMap = mutableMapOf(Pair(231, true)) + + val finalFusedDownload = runProcessInstall(fusedDownload) + + assertEquals(Status.INSTALLATION_ISSUE, finalFusedDownload?.status) + } + + @Test + fun processInstall_reportsMissingDownloadUrlsAsInstallationIssue() = runTest { + val fusedDownload = initTest(downloadUrlList = mutableListOf()) + + val finalFusedDownload = runProcessInstall(fusedDownload) + + assertEquals(Status.INSTALLATION_ISSUE, finalFusedDownload?.status) + } + + @Test + fun processInstall_returnsSuccessWhenInternalExceptionOccurs() = runTest { + val fusedDownload = initTest() + fakeFusedManagerRepository.forceCrash = true + + val result = workRunner.processInstall(fusedDownload.id, false) { + // _ignored_ + } + val finalFusedDownload = fakeFusedDownloadDAO.getDownloadById(fusedDownload.id) + + assertTrue(result.isSuccess) + assertEquals(ResultStatus.OK, result.getOrNull()) + assertTrue(finalFusedDownload == null || fusedDownload.status == Status.INSTALLATION_ISSUE) + } + + @Test + fun processInstall_reportsDownloadFailure() = runTest { + val fusedDownload = initTest() + fakeFusedManagerRepository.willDownloadFail = true + + val finalFusedDownload = runProcessInstall(fusedDownload) + + assertEquals(Status.INSTALLATION_ISSUE, finalFusedDownload?.status) + } + + @Test + fun processInstall_reportsInstallFailure() = runTest { + val fusedDownload = initTest() + fakeFusedManagerRepository.willInstallFail = true + + val finalFusedDownload = runProcessInstall(fusedDownload) + + assertEquals(Status.INSTALLATION_ISSUE, finalFusedDownload?.status) + } + + @Test + fun processInstall_updatesDownloadManagerStateForDownloadingItems() = runTest { + val fusedDownload = initTest() + fusedDownload.status = Status.DOWNLOADING + fusedDownload.downloadURLList = mutableListOf() + fusedDownload.downloadIdMap = mutableMapOf(231L to false, 232L to false) + + workRunner.processInstall(fusedDownload.id, false) { + // _ignored_ + } + + verify { downloadManagerUtils.updateDownloadStatus(231L) } + verify { downloadManagerUtils.updateDownloadStatus(232L) } + } + + private suspend fun initTest( + packageName: String? = null, + downloadUrlList: MutableList? = null + ): AppInstall { + val fusedDownload = createFusedDownload(packageName, downloadUrlList) + fakeFusedDownloadDAO.addDownload(fusedDownload) + return fusedDownload + } + + private suspend fun runProcessInstall(appInstall: AppInstall): AppInstall? { + workRunner.processInstall(appInstall.id, false) { + // _ignored_ + } + return fakeFusedDownloadDAO.getDownloadById(appInstall.id) + } + + private fun createFusedDownload( + packageName: String? = null, + downloadUrlList: MutableList? = null + ) = AppInstall( + id = "121", + status = Status.AWAITING, + downloadURLList = downloadUrlList ?: mutableListOf("apk1", "apk2"), + packageName = packageName ?: "com.unit.test" + ) +} -- GitLab From 2fa3c7da900745082f09a32e9c4a708ab22c9da1 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Thu, 12 Mar 2026 23:49:45 +0600 Subject: [PATCH 07/43] refactor: move Application -> AppInstall conversion logic to AppInstallRequestFactory Move AppInstall construction into a focused factory so request mapping can be tested directly without changing how update detection or enqueueing works. --- .../workmanager/AppInstallProcessor.kt | 27 +---- .../workmanager/AppInstallRequestFactory.kt | 54 +++++++++ .../AppInstallProcessorTest.kt | 9 +- .../AppInstallRequestFactoryTest.kt | 108 ++++++++++++++++++ 4 files changed, 172 insertions(+), 26 deletions(-) create mode 100644 app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallRequestFactory.kt create mode 100644 app/src/test/java/foundation/e/apps/installProcessor/AppInstallRequestFactoryTest.kt 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 797dcf951..c2317d2d5 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 @@ -27,7 +27,6 @@ import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.application.ApplicationRepository import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.ResultStatus -import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Type import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppInstallComponents @@ -46,7 +45,7 @@ import kotlinx.coroutines.DelicateCoroutinesApi import timber.log.Timber import javax.inject.Inject -@Suppress("LongParameterList") +@Suppress("LongParameterList") // FIXME: Remove suppression and fix detekt class AppInstallProcessor @Inject constructor( @ApplicationContext private val context: Context, private val appInstallComponents: AppInstallComponents, @@ -59,6 +58,7 @@ class AppInstallProcessor @Inject constructor( private val networkStatusChecker: NetworkStatusChecker, private val appInstallAgeLimitGate: AppInstallAgeLimitGate, private val appInstallWorkRunner: AppInstallWorkRunner, + private val appInstallRequestFactory: AppInstallRequestFactory, ) { @Inject lateinit var appManager: AppManager @@ -73,28 +73,7 @@ class AppInstallProcessor @Inject constructor( application: Application, isAnUpdate: Boolean = false ): Boolean { - val appInstall = AppInstall( - application._id, - application.source, - application.status, - application.name, - application.package_name, - mutableListOf(), - mutableMapOf(), - application.status, - application.type, - application.icon_image_path, - application.latest_version_code, - application.offer_type, - application.isFree, - application.originalSize - ).also { - it.contentRating = application.contentRating - } - - if (appInstall.type == Type.PWA || application.source == Source.SYSTEM_APP) { - appInstall.downloadURLList = mutableListOf(application.url) - } + val appInstall = appInstallRequestFactory.create(application) val isUpdate = isAnUpdate || application.status == Status.UPDATABLE || diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallRequestFactory.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallRequestFactory.kt new file mode 100644 index 000000000..2045c39bf --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallRequestFactory.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.data.install.workmanager + +import foundation.e.apps.data.application.data.Application +import foundation.e.apps.data.enums.Source +import foundation.e.apps.data.enums.Type +import foundation.e.apps.data.install.models.AppInstall +import javax.inject.Inject + +class AppInstallRequestFactory @Inject constructor() { + fun create(application: Application): AppInstall { + val appInstall = AppInstall( + application._id, + application.source, + application.status, + application.name, + application.package_name, + mutableListOf(), + mutableMapOf(), + application.status, + application.type, + application.icon_image_path, + application.latest_version_code, + application.offer_type, + application.isFree, + application.originalSize + ).also { + it.contentRating = application.contentRating + } + + if (appInstall.type == Type.PWA || application.source == Source.SYSTEM_APP) { + appInstall.downloadURLList = mutableListOf(application.url) + } + + return appInstall + } +} diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt index 0055f81be..d117f2edc 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt @@ -40,6 +40,7 @@ import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager import foundation.e.apps.data.install.workmanager.AppInstallAgeLimitGate +import foundation.e.apps.data.install.workmanager.AppInstallRequestFactory import foundation.e.apps.data.install.workmanager.AppInstallProcessor import foundation.e.apps.data.install.workmanager.AppInstallWorkRunner import foundation.e.apps.data.install.workmanager.AppUpdateCompletionHandler @@ -127,6 +128,7 @@ class AppInstallProcessorTest { private lateinit var appInstallAgeLimitGate: AppInstallAgeLimitGate private lateinit var appUpdateCompletionHandler: AppUpdateCompletionHandler private lateinit var appInstallWorkRunner: AppInstallWorkRunner + private lateinit var appInstallRequestFactory: AppInstallRequestFactory private var isInstallWorkManagerMocked = false @@ -140,6 +142,7 @@ class AppInstallProcessorTest { coEvery { sessionRepository.awaitUser() } returns User.NO_GOOGLE coEvery { playStoreAuthStore.awaitAuthData() } returns null appEventDispatcher = FakeAppEventDispatcher() + appInstallRequestFactory = AppInstallRequestFactory() storageSpaceChecker = mockk(relaxed = true) parentalControlAuthGateway = mockk(relaxed = true) updatesTracker = mockk(relaxed = true) @@ -185,7 +188,8 @@ class AppInstallProcessorTest { storageSpaceChecker, networkStatusChecker, appInstallAgeLimitGate, - appInstallWorkRunner + appInstallWorkRunner, + appInstallRequestFactory ) } @@ -848,7 +852,8 @@ class AppInstallProcessorTest { storageSpaceChecker, networkStatusChecker, ageLimitGate, - workRunner + workRunner, + appInstallRequestFactory ) } diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallRequestFactoryTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallRequestFactoryTest.kt new file mode 100644 index 000000000..53c5a28ef --- /dev/null +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallRequestFactoryTest.kt @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.installProcessor + +import com.aurora.gplayapi.data.models.ContentRating +import foundation.e.apps.data.application.data.Application +import foundation.e.apps.data.enums.Source +import foundation.e.apps.data.enums.Type +import foundation.e.apps.data.install.workmanager.AppInstallRequestFactory +import foundation.e.apps.domain.model.install.Status +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +class AppInstallRequestFactoryTest { + private lateinit var factory: AppInstallRequestFactory + + @Before + fun setup() { + factory = AppInstallRequestFactory() + } + + @Test + fun create_copiesExpectedFields() { + val application = Application( + _id = "123", + source = Source.PLAY_STORE, + status = Status.AWAITING, + name = "Example", + package_name = "com.example.app", + type = Type.NATIVE, + icon_image_path = "icon.png", + latest_version_code = 42, + offer_type = 1, + isFree = false, + originalSize = 2048L + ) + + val appInstall = factory.create(application) + + assertEquals("123", appInstall.id) + assertEquals(Source.PLAY_STORE, appInstall.source) + assertEquals(Status.AWAITING, appInstall.status) + assertEquals("Example", appInstall.name) + assertEquals("com.example.app", appInstall.packageName) + assertEquals(Type.NATIVE, appInstall.type) + assertEquals("icon.png", appInstall.iconImageUrl) + assertEquals(42, appInstall.versionCode) + assertEquals(1, appInstall.offerType) + assertEquals(false, appInstall.isFree) + assertEquals(2048L, appInstall.appSize) + } + + @Test + fun create_setsContentRating() { + val contentRating = ContentRating() + val application = Application(contentRating = contentRating) + + val appInstall = factory.create(application) + + assertEquals(contentRating, appInstall.contentRating) + } + + @Test + fun create_initializesDirectUrlForPwa() { + val application = Application(type = Type.PWA, url = "https://example.com") + + val appInstall = factory.create(application) + + assertEquals(mutableListOf("https://example.com"), appInstall.downloadURLList) + } + + @Test + fun create_initializesDirectUrlForSystemApp() { + val application = Application(source = Source.SYSTEM_APP, url = "file://app.apk") + + val appInstall = factory.create(application) + + assertEquals(mutableListOf("file://app.apk"), appInstall.downloadURLList) + } + + @Test + fun create_doesNotForceDirectUrlForNativeNonSystemApp() { + val application = + Application(source = Source.PLAY_STORE, type = Type.NATIVE, url = "ignored") + + val appInstall = factory.create(application) + + assertTrue(appInstall.downloadURLList.isEmpty()) + } +} -- GitLab From ec82ac884dbc264dcff35c77464ebfe3302831ee Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Fri, 13 Mar 2026 00:03:37 +0600 Subject: [PATCH 08/43] refactor: move pre-enqueue and enqueue logic from AppInstallProcessor to a separate class Move enqueue validation and download-link refresh orchestration behind a dedicated coordinator so preflight rules can be exercised directly while the processor stays focused on delegation. --- .../workmanager/AppInstallProcessor.kt | 154 +--------- .../workmanager/AppInstallStartCoordinator.kt | 186 ++++++++++++ .../AppInstallProcessorTest.kt | 50 ++-- .../AppInstallStartCoordinatorTest.kt | 282 ++++++++++++++++++ 4 files changed, 502 insertions(+), 170 deletions(-) create mode 100644 app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallStartCoordinator.kt create mode 100644 app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt 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 c2317d2d5..23545c09c 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 @@ -18,51 +18,21 @@ package foundation.e.apps.data.install.workmanager -import android.content.Context import androidx.annotation.VisibleForTesting -import com.aurora.gplayapi.exceptions.InternalException -import dagger.hilt.android.qualifiers.ApplicationContext -import foundation.e.apps.R -import foundation.e.apps.data.ResultSupreme -import foundation.e.apps.data.application.ApplicationRepository import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.ResultStatus -import foundation.e.apps.data.enums.Type -import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppInstallComponents -import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.install.notification.StorageNotificationManager -import foundation.e.apps.data.install.wrapper.AppEventDispatcher -import foundation.e.apps.data.install.wrapper.NetworkStatusChecker -import foundation.e.apps.data.install.wrapper.StorageSpaceChecker -import foundation.e.apps.data.playstore.utils.GplayHttpRequestException -import foundation.e.apps.data.preference.PlayStoreAuthStore -import foundation.e.apps.domain.model.User import foundation.e.apps.domain.model.install.Status -import foundation.e.apps.domain.preferences.SessionRepository -import kotlinx.coroutines.DelicateCoroutinesApi -import timber.log.Timber import javax.inject.Inject @Suppress("LongParameterList") // FIXME: Remove suppression and fix detekt class AppInstallProcessor @Inject constructor( - @ApplicationContext private val context: Context, private val appInstallComponents: AppInstallComponents, - private val applicationRepository: ApplicationRepository, - private val sessionRepository: SessionRepository, - private val playStoreAuthStore: PlayStoreAuthStore, - private val storageNotificationManager: StorageNotificationManager, - private val appEventDispatcher: AppEventDispatcher, - private val storageSpaceChecker: StorageSpaceChecker, - private val networkStatusChecker: NetworkStatusChecker, - private val appInstallAgeLimitGate: AppInstallAgeLimitGate, + private val appInstallStartCoordinator: AppInstallStartCoordinator, private val appInstallWorkRunner: AppInstallWorkRunner, private val appInstallRequestFactory: AppInstallRequestFactory, ) { - @Inject - lateinit var appManager: AppManager - /** * creates [AppInstall] from [Application] and enqueues into WorkManager to run install process. * @param application represents the app info which will be installed @@ -94,132 +64,14 @@ class AppInstallProcessor @Inject constructor( isAnUpdate: Boolean = false, isSystemApp: Boolean = false ): Boolean { - val uniqueWorkName = InstallWorkManager.getUniqueWorkName(appInstall.packageName) - - return try { - val user = sessionRepository.awaitUser() - if (!isSystemApp && (user == User.GOOGLE || user == User.ANONYMOUS)) { - val authData = playStoreAuthStore.awaitAuthData() - if (!appInstall.isFree && authData?.isAnonymous == true) { - appEventDispatcher.dispatch( - AppEvent.ErrorMessageEvent(R.string.paid_app_anonymous_message) - ) - } - } - - if (!canEnqueue(appInstall)) return false - - appInstallComponents.appManagerWrapper.updateAwaiting(appInstall) - - // Use only for update work for now. For installation work, see InstallOrchestrator#observeDownloads() - if (isAnUpdate) { - InstallWorkManager.enqueueWork(context, appInstall, true) - Timber.d("UPDATE: Successfully enqueued unique work: $uniqueWorkName") - } - true - } catch (e: Exception) { - Timber.e(e, "UPDATE: Failed to enqueue unique work for ${appInstall.packageName}") - appInstallComponents.appManagerWrapper.installationIssue(appInstall) - false - } + return appInstallStartCoordinator.enqueue(appInstall, isAnUpdate, isSystemApp) } @VisibleForTesting suspend fun canEnqueue(appInstall: AppInstall): Boolean { - if (appInstall.type != Type.PWA && !updateDownloadUrls(appInstall)) { - return false - } - - if (!appInstallComponents.appManagerWrapper.addDownload(appInstall)) { - Timber.i("Update adding ABORTED! status") - return false - } - - if (!appInstallAgeLimitGate.allow(appInstall)) { - return false - } - - if (!networkStatusChecker.isNetworkAvailable()) { - appInstallComponents.appManagerWrapper.installationIssue(appInstall) - appEventDispatcher.dispatch(AppEvent.NoInternetEvent(false)) - return false - } - - if (storageSpaceChecker.spaceMissing(appInstall) > 0) { - Timber.d("Storage is not available for: ${appInstall.name} size: ${appInstall.appSize}") - storageNotificationManager.showNotEnoughSpaceNotification(appInstall) - appInstallComponents.appManagerWrapper.installationIssue(appInstall) - appEventDispatcher.dispatch(AppEvent.ErrorMessageEvent(R.string.not_enough_storage)) - return false - } - - return true - } - - // returns TRUE if updating urls is successful, otherwise false. - private suspend fun updateDownloadUrls(appInstall: AppInstall): Boolean { - try { - updateFusedDownloadWithAppDownloadLink(appInstall) - } catch (e: InternalException.AppNotPurchased) { - if (appInstall.isFree) { - handleAppRestricted(appInstall) - return false - } - appInstallComponents.appManagerWrapper.addFusedDownloadPurchaseNeeded(appInstall) - appEventDispatcher.dispatch(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 - ) - return false - } - return true - } - - private suspend fun handleAppRestricted(appInstall: AppInstall) { - appEventDispatcher.dispatch(AppEvent.AppRestrictedOrUnavailable(appInstall)) - appManager.addDownload(appInstall) - appManager.updateUnavailable(appInstall) - } - - private suspend fun handleUpdateDownloadError( - appInstall: AppInstall, - message: String, - e: Exception - ) { - Timber.e(e, "Updating download Urls failed for $message") - appEventDispatcher.dispatch( - AppEvent.UpdateEvent( - ResultSupreme.WorkError( - ResultStatus.UNKNOWN, - appInstall - ) - ) - ) - } - - private suspend fun updateFusedDownloadWithAppDownloadLink( - appInstall: AppInstall - ) { - applicationRepository.updateFusedDownloadWithDownloadingInfo( - appInstall.source, - appInstall - ) + return appInstallStartCoordinator.canEnqueue(appInstall) } - @OptIn(DelicateCoroutinesApi::class) suspend fun processInstall( fusedDownloadId: String, isItUpdateWork: Boolean, diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallStartCoordinator.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallStartCoordinator.kt new file mode 100644 index 000000000..7291aa883 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallStartCoordinator.kt @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.data.install.workmanager + +import android.content.Context +import com.aurora.gplayapi.exceptions.InternalException +import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.apps.R +import foundation.e.apps.data.ResultSupreme +import foundation.e.apps.data.application.ApplicationRepository +import foundation.e.apps.data.enums.ResultStatus +import foundation.e.apps.data.enums.Type +import foundation.e.apps.data.event.AppEvent +import foundation.e.apps.data.install.AppManager +import foundation.e.apps.data.install.AppManagerWrapper +import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.install.notification.StorageNotificationManager +import foundation.e.apps.data.install.wrapper.AppEventDispatcher +import foundation.e.apps.data.install.wrapper.NetworkStatusChecker +import foundation.e.apps.data.install.wrapper.StorageSpaceChecker +import foundation.e.apps.data.playstore.utils.GplayHttpRequestException +import foundation.e.apps.data.preference.PlayStoreAuthStore +import foundation.e.apps.domain.model.User +import foundation.e.apps.domain.preferences.SessionRepository +import timber.log.Timber +import javax.inject.Inject + +@Suppress("LongParameterList", "TooGenericExceptionCaught", "ReturnCount") // FIXME: Remove suppression and fix detekt +class AppInstallStartCoordinator @Inject constructor( + @ApplicationContext private val context: Context, + private val appManagerWrapper: AppManagerWrapper, + private val applicationRepository: ApplicationRepository, + private val sessionRepository: SessionRepository, + private val playStoreAuthStore: PlayStoreAuthStore, + private val storageNotificationManager: StorageNotificationManager, + private val appInstallAgeLimitGate: AppInstallAgeLimitGate, + private val appEventDispatcher: AppEventDispatcher, + private val storageSpaceChecker: StorageSpaceChecker, + private val networkStatusChecker: NetworkStatusChecker, + private val appManager: AppManager, +) { + suspend fun enqueue( + appInstall: AppInstall, + isAnUpdate: Boolean = false, + isSystemApp: Boolean = false + ): Boolean { + val uniqueWorkName = InstallWorkManager.getUniqueWorkName(appInstall.packageName) + + return try { + val user = sessionRepository.awaitUser() + if (!isSystemApp && (user == User.GOOGLE || user == User.ANONYMOUS)) { + val authData = playStoreAuthStore.awaitAuthData() + if (!appInstall.isFree && authData?.isAnonymous == true) { + appEventDispatcher.dispatch( + AppEvent.ErrorMessageEvent(R.string.paid_app_anonymous_message) + ) + } + } + + if (!canEnqueue(appInstall)) return false + + appManagerWrapper.updateAwaiting(appInstall) + + // Use only for update work for now. For installation work, see InstallOrchestrator#observeDownloads() + if (isAnUpdate) { + InstallWorkManager.enqueueWork(context, appInstall, true) + Timber.d("UPDATE: Successfully enqueued unique work: $uniqueWorkName") + } + true + } catch (e: Exception) { + Timber.e( + e, + "Enqueuing App install work is failed for ${appInstall.packageName} exception: ${e.localizedMessage}" + ) + appManagerWrapper.installationIssue(appInstall) + false + } + } + + suspend fun canEnqueue(appInstall: AppInstall): Boolean { + if (appInstall.type != Type.PWA && !updateDownloadUrls(appInstall)) { + return false + } + + if (!appManagerWrapper.addDownload(appInstall)) { + Timber.i("Update adding ABORTED! status") + return false + } + + if (!appInstallAgeLimitGate.allow(appInstall)) { + return false + } + + if (!networkStatusChecker.isNetworkAvailable()) { + appManagerWrapper.installationIssue(appInstall) + appEventDispatcher.dispatch(AppEvent.NoInternetEvent(false)) + return false + } + + if (storageSpaceChecker.spaceMissing(appInstall) > 0) { + Timber.d("Storage is not available for: ${appInstall.name} size: ${appInstall.appSize}") + storageNotificationManager.showNotEnoughSpaceNotification(appInstall) + appManagerWrapper.installationIssue(appInstall) + appEventDispatcher.dispatch(AppEvent.ErrorMessageEvent(R.string.not_enough_storage)) + return false + } + + return true + } + + private suspend fun updateDownloadUrls(appInstall: AppInstall): Boolean { + try { + updateFusedDownloadWithAppDownloadLink(appInstall) + } catch (_: InternalException.AppNotPurchased) { + if (appInstall.isFree) { + handleAppRestricted(appInstall) + return false + } + appManagerWrapper.addFusedDownloadPurchaseNeeded(appInstall) + appEventDispatcher.dispatch(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 + ) + return false + } + return true + } + + private suspend fun handleAppRestricted(appInstall: AppInstall) { + appEventDispatcher.dispatch(AppEvent.AppRestrictedOrUnavailable(appInstall)) + appManager.addDownload(appInstall) + appManager.updateUnavailable(appInstall) + } + + private suspend fun handleUpdateDownloadError( + appInstall: AppInstall, + message: String, + e: Exception + ) { + Timber.e(e, "Updating download Urls failed for $message") + appEventDispatcher.dispatch( + AppEvent.UpdateEvent( + ResultSupreme.WorkError( + ResultStatus.UNKNOWN, + appInstall + ) + ) + ) + } + + private suspend fun updateFusedDownloadWithAppDownloadLink(appInstall: AppInstall) { + applicationRepository.updateFusedDownloadWithDownloadingInfo( + appInstall.source, + appInstall + ) + } +} diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt index d117f2edc..190c99107 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt @@ -40,8 +40,9 @@ import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager import foundation.e.apps.data.install.workmanager.AppInstallAgeLimitGate -import foundation.e.apps.data.install.workmanager.AppInstallRequestFactory import foundation.e.apps.data.install.workmanager.AppInstallProcessor +import foundation.e.apps.data.install.workmanager.AppInstallRequestFactory +import foundation.e.apps.data.install.workmanager.AppInstallStartCoordinator import foundation.e.apps.data.install.workmanager.AppInstallWorkRunner import foundation.e.apps.data.install.workmanager.AppUpdateCompletionHandler import foundation.e.apps.data.install.workmanager.InstallWorkManager @@ -126,6 +127,7 @@ class AppInstallProcessorTest { private lateinit var updatesNotificationSender: UpdatesNotificationSender private lateinit var networkStatusChecker: NetworkStatusChecker private lateinit var appInstallAgeLimitGate: AppInstallAgeLimitGate + private lateinit var appInstallStartCoordinator: AppInstallStartCoordinator private lateinit var appUpdateCompletionHandler: AppUpdateCompletionHandler private lateinit var appInstallWorkRunner: AppInstallWorkRunner private lateinit var appInstallRequestFactory: AppInstallRequestFactory @@ -160,6 +162,19 @@ class AppInstallProcessorTest { appEventDispatcher, parentalControlAuthGateway ) + appInstallStartCoordinator = AppInstallStartCoordinator( + context, + fakeFusedManagerRepository, + applicationRepository, + sessionRepository, + playStoreAuthStore, + storageNotificationManager, + appInstallAgeLimitGate, + appEventDispatcher, + storageSpaceChecker, + networkStatusChecker, + fakeFusedManager + ) appUpdateCompletionHandler = AppUpdateCompletionHandler( context, appInstallRepository, @@ -178,16 +193,8 @@ class AppInstallProcessorTest { ) appInstallProcessor = AppInstallProcessor( - context, appInstallComponents, - applicationRepository, - sessionRepository, - playStoreAuthStore, - storageNotificationManager, - appEventDispatcher, - storageSpaceChecker, - networkStatusChecker, - appInstallAgeLimitGate, + appInstallStartCoordinator, appInstallWorkRunner, appInstallRequestFactory ) @@ -835,23 +842,28 @@ class AppInstallProcessorTest { appEventDispatcher, parentalControlAuthGateway ) - val workRunner = AppInstallWorkRunner( - appInstallRepository, - appManagerWrapper, - mockk(relaxed = true), - appUpdateCompletionHandler - ) - return AppInstallProcessor( + val startCoordinator = AppInstallStartCoordinator( context, - appInstallComponents, + appManagerWrapper, applicationRepository, sessionRepository, playStoreAuthStore, storageNotificationManager, + ageLimitGate, appEventDispatcher, storageSpaceChecker, networkStatusChecker, - ageLimitGate, + fakeFusedManager + ) + val workRunner = AppInstallWorkRunner( + appInstallRepository, + appManagerWrapper, + mockk(relaxed = true), + appUpdateCompletionHandler + ) + return AppInstallProcessor( + appInstallComponents, + startCoordinator, workRunner, appInstallRequestFactory ) diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt new file mode 100644 index 000000000..2385a90a2 --- /dev/null +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.installProcessor + +import android.content.Context +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.exceptions.InternalException +import foundation.e.apps.R +import foundation.e.apps.data.application.ApplicationRepository +import foundation.e.apps.data.enums.Source +import foundation.e.apps.data.enums.Type +import foundation.e.apps.data.event.AppEvent +import foundation.e.apps.data.install.AppManager +import foundation.e.apps.data.install.AppManagerWrapper +import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.install.notification.StorageNotificationManager +import foundation.e.apps.data.install.workmanager.AppInstallAgeLimitGate +import foundation.e.apps.data.install.workmanager.AppInstallStartCoordinator +import foundation.e.apps.data.install.workmanager.InstallWorkManager +import foundation.e.apps.data.install.wrapper.NetworkStatusChecker +import foundation.e.apps.data.install.wrapper.StorageSpaceChecker +import foundation.e.apps.data.playstore.utils.GplayHttpRequestException +import foundation.e.apps.data.preference.PlayStoreAuthStore +import foundation.e.apps.domain.model.User +import foundation.e.apps.domain.model.install.Status +import foundation.e.apps.domain.preferences.SessionRepository +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.justRun +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.unmockkObject +import io.mockk.verify +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class AppInstallStartCoordinatorTest { + private lateinit var context: Context + private lateinit var appManagerWrapper: AppManagerWrapper + private lateinit var applicationRepository: ApplicationRepository + private lateinit var sessionRepository: SessionRepository + private lateinit var playStoreAuthStore: PlayStoreAuthStore + private lateinit var storageNotificationManager: StorageNotificationManager + private lateinit var appInstallAgeLimitGate: AppInstallAgeLimitGate + private lateinit var appEventDispatcher: FakeAppEventDispatcher + private lateinit var storageSpaceChecker: StorageSpaceChecker + private lateinit var networkStatusChecker: NetworkStatusChecker + private lateinit var appManager: AppManager + private lateinit var coordinator: AppInstallStartCoordinator + + @Before + fun setup() { + context = mockk(relaxed = true) + appManagerWrapper = mockk(relaxed = true) + applicationRepository = mockk(relaxed = true) + sessionRepository = mockk(relaxed = true) + playStoreAuthStore = mockk(relaxed = true) + storageNotificationManager = mockk(relaxed = true) + appInstallAgeLimitGate = mockk(relaxed = true) + appEventDispatcher = FakeAppEventDispatcher() + storageSpaceChecker = mockk(relaxed = true) + networkStatusChecker = mockk(relaxed = true) + appManager = mockk(relaxed = true) + coEvery { sessionRepository.awaitUser() } returns User.NO_GOOGLE + coEvery { playStoreAuthStore.awaitAuthData() } returns null + coordinator = AppInstallStartCoordinator( + context, + appManagerWrapper, + applicationRepository, + sessionRepository, + playStoreAuthStore, + storageNotificationManager, + appInstallAgeLimitGate, + appEventDispatcher, + storageSpaceChecker, + networkStatusChecker, + appManager + ) + } + + @Test + fun canEnqueue_returnsTrueWhenAllChecksPass() = runTest { + val appInstall = createPwaInstall() + + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + coEvery { appInstallAgeLimitGate.allow(appInstall) } returns true + every { networkStatusChecker.isNetworkAvailable() } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + + val result = coordinator.canEnqueue(appInstall) + + assertTrue(result) + } + + @Test + fun canEnqueue_returnsFalseWhenNetworkUnavailable() = runTest { + val appInstall = createPwaInstall() + + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + coEvery { appInstallAgeLimitGate.allow(appInstall) } returns true + every { networkStatusChecker.isNetworkAvailable() } returns false + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + + val result = coordinator.canEnqueue(appInstall) + + assertFalse(result) + coVerify { appManagerWrapper.installationIssue(appInstall) } + } + + @Test + fun canEnqueue_returnsFalseWhenStorageMissing() = runTest { + val appInstall = createPwaInstall() + + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + coEvery { appInstallAgeLimitGate.allow(appInstall) } returns true + every { networkStatusChecker.isNetworkAvailable() } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 100L + + val result = coordinator.canEnqueue(appInstall) + + assertFalse(result) + verify { storageNotificationManager.showNotEnoughSpaceNotification(appInstall) } + coVerify { appManagerWrapper.installationIssue(appInstall) } + } + + @Test + fun canEnqueue_returnsFalseWhenAddDownloadFails() = runTest { + val appInstall = createPwaInstall() + + coEvery { appManagerWrapper.addDownload(appInstall) } returns false + + val result = coordinator.canEnqueue(appInstall) + + assertFalse(result) + coVerify(exactly = 0) { appInstallAgeLimitGate.allow(any()) } + } + + @Test + fun enqueue_warnsAnonymousPaidUsersWithoutAborting() = runTest { + val appInstall = createPwaInstall(isFree = false) + + mockkObject(InstallWorkManager) + try { + coEvery { sessionRepository.awaitUser() } returns User.ANONYMOUS + coEvery { + playStoreAuthStore.awaitAuthData() + } returns AuthData(email = "anon@example.com", isAnonymous = true) + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + coEvery { appInstallAgeLimitGate.allow(appInstall) } returns true + every { networkStatusChecker.isNetworkAvailable() } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + justRun { InstallWorkManager.enqueueWork(any(), any(), any()) } + + val result = coordinator.enqueue(appInstall) + + assertTrue(result) + assertTrue(appEventDispatcher.events.any { + it is AppEvent.ErrorMessageEvent && it.data == R.string.paid_app_anonymous_message + }) + coVerify { appManagerWrapper.updateAwaiting(appInstall) } + verify(exactly = 0) { InstallWorkManager.enqueueWork(any(), any(), any()) } + } finally { + unmockkObject(InstallWorkManager) + } + } + + @Test + fun canEnqueue_handlesFreeAppNotPurchasedAsRestricted() = runTest { + val appInstall = createNativeInstall(isFree = true) + + coEvery { + applicationRepository.updateFusedDownloadWithDownloadingInfo( + Source.PLAY_STORE, + appInstall + ) + } throws InternalException.AppNotPurchased() + + val result = coordinator.canEnqueue(appInstall) + + assertFalse(result) + assertTrue(appEventDispatcher.events.any { it is AppEvent.AppRestrictedOrUnavailable }) + coVerify { appManager.addDownload(appInstall) } + coVerify { appManager.updateUnavailable(appInstall) } + } + + @Test + fun canEnqueue_handlesPaidAppNotPurchasedAsPurchaseNeeded() = runTest { + val appInstall = createNativeInstall(isFree = false) + + coEvery { + applicationRepository.updateFusedDownloadWithDownloadingInfo( + Source.PLAY_STORE, + appInstall + ) + } throws InternalException.AppNotPurchased() + + val result = coordinator.canEnqueue(appInstall) + + assertFalse(result) + coVerify { appManagerWrapper.addFusedDownloadPurchaseNeeded(appInstall) } + assertTrue(appEventDispatcher.events.any { it is AppEvent.AppPurchaseEvent }) + } + + @Test + fun canEnqueue_returnsFalseWhenDownloadUrlRefreshThrowsHttpError() = runTest { + val appInstall = createNativeInstall() + + coEvery { + applicationRepository.updateFusedDownloadWithDownloadingInfo( + Source.PLAY_STORE, + appInstall + ) + } throws GplayHttpRequestException(403, "forbidden") + + val result = coordinator.canEnqueue(appInstall) + + assertFalse(result) + assertTrue(appEventDispatcher.events.any { it is AppEvent.UpdateEvent }) + coVerify(exactly = 0) { appManagerWrapper.addDownload(appInstall) } + } + + @Test + fun canEnqueue_keepsGoingWhenDownloadUrlRefreshThrowsIllegalState() = runTest { + val appInstall = createNativeInstall() + + coEvery { + applicationRepository.updateFusedDownloadWithDownloadingInfo( + Source.PLAY_STORE, + appInstall + ) + } throws IllegalStateException("boom") + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + coEvery { appInstallAgeLimitGate.allow(appInstall) } returns true + every { networkStatusChecker.isNetworkAvailable() } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + + val result = coordinator.canEnqueue(appInstall) + + assertTrue(result) + coVerify { appManagerWrapper.addDownload(appInstall) } + } + + private fun createPwaInstall(isFree: Boolean = true) = AppInstall( + type = Type.PWA, + id = "123", + status = Status.AWAITING, + downloadURLList = mutableListOf("apk"), + packageName = "com.example.app", + isFree = isFree + ) + + private fun createNativeInstall(isFree: Boolean = true) = AppInstall( + type = Type.NATIVE, + source = Source.PLAY_STORE, + id = "123", + status = Status.AWAITING, + packageName = "com.example.app", + isFree = isFree + ) +} -- GitLab From ae5b66ed0597a37d4951afbcc73dee7761d6a6ec Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Fri, 13 Mar 2026 00:10:36 +0600 Subject: [PATCH 09/43] refactor: make AppInstallProcessor to facade behavior Keep the processor focused on request-to-collaborator delegation so its tests describe the public contract while the extracted components own the detailed behavior coverage. --- .../workmanager/AppInstallProcessor.kt | 6 - .../AppInstallProcessorTest.kt | 885 +----------------- 2 files changed, 40 insertions(+), 851 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 23545c09c..3be4b212e 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 @@ -18,7 +18,6 @@ package foundation.e.apps.data.install.workmanager -import androidx.annotation.VisibleForTesting import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.install.AppInstallComponents @@ -67,11 +66,6 @@ class AppInstallProcessor @Inject constructor( return appInstallStartCoordinator.enqueue(appInstall, isAnUpdate, isSystemApp) } - @VisibleForTesting - suspend fun canEnqueue(appInstall: AppInstall): Boolean { - return appInstallStartCoordinator.canEnqueue(appInstall) - } - suspend fun processInstall( fusedDownloadId: String, isItUpdateWork: Boolean, diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt index 190c99107..6755c9f32 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt @@ -1,5 +1,5 @@ /* - * Copyright MURENA SAS 2023 + * Copyright MURENA SAS 2026 * Apps Quickly and easily install Android apps onto your device! * * This program is free software: you can redistribute it and/or modify @@ -18,179 +18,55 @@ package foundation.e.apps.installProcessor -import android.content.Context import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import androidx.work.Operation -import com.aurora.gplayapi.data.models.AuthData -import com.google.common.util.concurrent.Futures -import foundation.e.apps.R -import foundation.e.apps.data.ResultSupreme -import foundation.e.apps.data.application.ApplicationRepository import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Type -import foundation.e.apps.data.event.AppEvent import foundation.e.apps.domain.model.install.Status -import foundation.e.apps.data.fdroid.FDroidRepository import foundation.e.apps.data.install.AppInstallComponents import foundation.e.apps.data.install.AppInstallRepository -import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.install.notification.StorageNotificationManager -import foundation.e.apps.data.install.workmanager.AppInstallAgeLimitGate import foundation.e.apps.data.install.workmanager.AppInstallProcessor import foundation.e.apps.data.install.workmanager.AppInstallRequestFactory import foundation.e.apps.data.install.workmanager.AppInstallStartCoordinator import foundation.e.apps.data.install.workmanager.AppInstallWorkRunner -import foundation.e.apps.data.install.workmanager.AppUpdateCompletionHandler -import foundation.e.apps.data.install.workmanager.InstallWorkManager -import foundation.e.apps.data.install.wrapper.AppEventDispatcher -import foundation.e.apps.data.install.wrapper.NetworkStatusChecker -import foundation.e.apps.data.install.wrapper.ParentalControlAuthGateway -import foundation.e.apps.data.install.wrapper.StorageSpaceChecker -import foundation.e.apps.data.install.wrapper.UpdatesNotificationSender -import foundation.e.apps.data.install.wrapper.UpdatesTracker -import foundation.e.apps.data.playstore.utils.GplayHttpRequestException -import foundation.e.apps.data.preference.PlayStoreAuthStore -import foundation.e.apps.domain.ValidateAppAgeLimitUseCase -import foundation.e.apps.domain.model.ContentRatingValidity -import foundation.e.apps.domain.model.User -import foundation.e.apps.domain.preferences.SessionRepository import foundation.e.apps.util.MainCoroutineRule import io.mockk.coEvery import io.mockk.coVerify -import io.mockk.every -import io.mockk.justRun import io.mockk.mockk -import io.mockk.mockkObject -import io.mockk.unmockkObject -import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue -import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mockito.Mock -import org.mockito.Mockito -import org.mockito.MockitoAnnotations -import org.mockito.kotlin.mock -import org.mockito.kotlin.whenever -import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) class AppInstallProcessorTest { - // Run tasks synchronously @Rule @JvmField val instantExecutorRule = InstantTaskExecutorRule() - // Sets the main coroutines dispatcher to a TestCoroutineScope for unit testing. - @ExperimentalCoroutinesApi @get:Rule var mainCoroutineRule = MainCoroutineRule() - private lateinit var fakeFusedDownloadDAO: FakeAppInstallDAO - private lateinit var appInstallRepository: AppInstallRepository - private lateinit var fakeFusedManagerRepository: FakeAppManagerWrapper - - @Mock - private lateinit var fakeFusedManager: AppManager - - @Mock - private lateinit var fakeFDroidRepository: FDroidRepository - - private lateinit var context: Context - - private lateinit var sessionRepository: SessionRepository - - private lateinit var playStoreAuthStore: PlayStoreAuthStore - - private lateinit var applicationRepository: ApplicationRepository - + private lateinit var appManagerWrapper: AppManagerWrapper private lateinit var appInstallProcessor: AppInstallProcessor - - @Mock - private lateinit var validateAppAgeRatingUseCase: ValidateAppAgeLimitUseCase - - @Mock - private lateinit var storageNotificationManager: StorageNotificationManager - - private lateinit var appEventDispatcher: FakeAppEventDispatcher - private lateinit var storageSpaceChecker: StorageSpaceChecker - private lateinit var parentalControlAuthGateway: ParentalControlAuthGateway - private lateinit var updatesTracker: UpdatesTracker - private lateinit var updatesNotificationSender: UpdatesNotificationSender - private lateinit var networkStatusChecker: NetworkStatusChecker - private lateinit var appInstallAgeLimitGate: AppInstallAgeLimitGate + private lateinit var appInstallRequestFactory: AppInstallRequestFactory private lateinit var appInstallStartCoordinator: AppInstallStartCoordinator - private lateinit var appUpdateCompletionHandler: AppUpdateCompletionHandler private lateinit var appInstallWorkRunner: AppInstallWorkRunner - private lateinit var appInstallRequestFactory: AppInstallRequestFactory - - private var isInstallWorkManagerMocked = false @Before fun setup() { - MockitoAnnotations.openMocks(this) - context = mockk(relaxed = true) - sessionRepository = mockk(relaxed = true) - playStoreAuthStore = mockk(relaxed = true) - applicationRepository = mockk(relaxed = true) - coEvery { sessionRepository.awaitUser() } returns User.NO_GOOGLE - coEvery { playStoreAuthStore.awaitAuthData() } returns null - appEventDispatcher = FakeAppEventDispatcher() - appInstallRequestFactory = AppInstallRequestFactory() - storageSpaceChecker = mockk(relaxed = true) - parentalControlAuthGateway = mockk(relaxed = true) - updatesTracker = mockk(relaxed = true) - updatesNotificationSender = mockk(relaxed = true) - networkStatusChecker = mockk(relaxed = true) - fakeFusedDownloadDAO = FakeAppInstallDAO() - appInstallRepository = AppInstallRepository(fakeFusedDownloadDAO) - fakeFusedManagerRepository = - FakeAppManagerWrapper(fakeFusedDownloadDAO, context, fakeFusedManager, fakeFDroidRepository) - val appInstallComponents = - AppInstallComponents(appInstallRepository, fakeFusedManagerRepository) - appInstallAgeLimitGate = AppInstallAgeLimitGate( - validateAppAgeRatingUseCase, - fakeFusedManagerRepository, - appEventDispatcher, - parentalControlAuthGateway - ) - appInstallStartCoordinator = AppInstallStartCoordinator( - context, - fakeFusedManagerRepository, - applicationRepository, - sessionRepository, - playStoreAuthStore, - storageNotificationManager, - appInstallAgeLimitGate, - appEventDispatcher, - storageSpaceChecker, - networkStatusChecker, - fakeFusedManager - ) - appUpdateCompletionHandler = AppUpdateCompletionHandler( - context, - appInstallRepository, - fakeFusedManagerRepository, - playStoreAuthStore, - updatesTracker, - updatesNotificationSender - ) - val downloadManager = - mockk(relaxed = true) - appInstallWorkRunner = AppInstallWorkRunner( - appInstallRepository, - fakeFusedManagerRepository, - downloadManager, - appUpdateCompletionHandler - ) + appManagerWrapper = mockk(relaxed = true) + val appInstallRepository = mockk(relaxed = true) + val appInstallComponents = AppInstallComponents(appInstallRepository, appManagerWrapper) + appInstallRequestFactory = mockk(relaxed = true) + appInstallStartCoordinator = mockk(relaxed = true) + appInstallWorkRunner = mockk(relaxed = true) appInstallProcessor = AppInstallProcessor( appInstallComponents, @@ -200,737 +76,56 @@ class AppInstallProcessorTest { ) } - @After - fun teardown() { - if (isInstallWorkManagerMocked) { - unmockkObject(InstallWorkManager) - isInstallWorkManagerMocked = false - } - } - - @Test - fun processInstallTest() = runTest { - val fusedDownload = initTest() - - val finalFusedDownload = runProcessInstall(fusedDownload) - assertTrue("processInstall", finalFusedDownload == null) - } - - private suspend fun initTest( - packageName: String? = null, - downloadUrlList: MutableList? = null - ): AppInstall { - val fusedDownload = createFusedDownload(packageName, downloadUrlList) - fakeFusedDownloadDAO.addDownload(fusedDownload) - return fusedDownload - } - - @Test - fun `processInstallTest when FusedDownload is already failed`() = runTest { - val fusedDownload = initTest() - fusedDownload.status = Status.BLOCKED - - val finalFusedDownload = runProcessInstall(fusedDownload) - assertEquals("processInstall", Status.BLOCKED, finalFusedDownload?.status) - } - - @Test - fun `processInstallTest when files are downloaded but not installed`() = runTest { - val fusedDownload = initTest() - fusedDownload.downloadIdMap = mutableMapOf(Pair(231, true)) - - val finalFusedDownload = runProcessInstall(fusedDownload) - assertTrue("processInstall", finalFusedDownload == null) - } - - @Test - fun `processInstallTest when packageName is empty and files are downloaded`() = runTest { - val fusedDownload = initTest(packageName = "") - fusedDownload.downloadIdMap = mutableMapOf(Pair(231, true)) - - val finalFusedDownload = runProcessInstall(fusedDownload) - assertEquals("processInstall", Status.INSTALLATION_ISSUE, finalFusedDownload?.status) - } - - @Test - fun `processInstallTest when downloadUrls are not available`() = runTest { - val fusedDownload = initTest(downloadUrlList = mutableListOf()) - - val finalFusedDownload = runProcessInstall(fusedDownload) - assertEquals("processInstall", Status.INSTALLATION_ISSUE, finalFusedDownload?.status) - } - - @Test - fun `processInstallTest when exception is occurred`() = runTest { - val fusedDownload = initTest() - fakeFusedManagerRepository.forceCrash = true - - val finalFusedDownload = runProcessInstall(fusedDownload) - assertTrue( - "processInstall", - finalFusedDownload == null || fusedDownload.status == Status.INSTALLATION_ISSUE - ) - } - - @Test - fun `processInstallTest when download is failed`() = runTest { - val fusedDownload = initTest() - fakeFusedManagerRepository.willDownloadFail = true - - val finalFusedDownload = runProcessInstall(fusedDownload) - assertEquals("processInstall", Status.INSTALLATION_ISSUE, finalFusedDownload?.status) - } - - @Test - fun `processInstallTest when install is failed`() = runTest { - val fusedDownload = initTest() - fakeFusedManagerRepository.willInstallFail = true - - val finalFusedDownload = runProcessInstall(fusedDownload) - assertEquals("processInstall", Status.INSTALLATION_ISSUE, finalFusedDownload?.status) - } - - @Test - fun `processInstallTest when age limit is satisfied`() = runTest { - val fusedDownload = initTest() - Mockito.`when`(validateAppAgeRatingUseCase(fusedDownload)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - - val finalFusedDownload = runProcessInstall(fusedDownload) - assertEquals("processInstall", finalFusedDownload, null) - } - - @Test - fun `processInstallTest when age limit is not satisfied`() = runTest { - val fusedDownload = initTest() - Mockito.`when`(validateAppAgeRatingUseCase(fusedDownload)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(false))) - - val finalFusedDownload = runProcessInstall(fusedDownload) - assertEquals("processInstall", finalFusedDownload, null) - } - - @Test - fun `enqueueFusedDownload warns anonymous paid users without aborting`() = runTest { - val appInstall = AppInstall( - type = foundation.e.apps.data.enums.Type.PWA, - id = "123", - status = Status.AWAITING, - downloadURLList = mutableListOf("apk"), - packageName = "com.example.paid", - isFree = false - ) - val appManagerWrapper = mockk(relaxed = true) - val processor = createProcessorForCanEnqueue(appManagerWrapper) - - mockkObject(InstallWorkManager) - try { - coEvery { sessionRepository.awaitUser() } returns User.ANONYMOUS - coEvery { - playStoreAuthStore.awaitAuthData() - } returns AuthData(email = "anon@example.com", isAnonymous = true) - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - every { networkStatusChecker.isNetworkAvailable() } returns true - every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L - justRun { InstallWorkManager.enqueueWork(any(), any(), any()) } - - val result = processor.enqueueFusedDownload(appInstall) - - assertTrue(result) - assertTrue(appEventDispatcher.events.any { - it is AppEvent.ErrorMessageEvent && it.data == R.string.paid_app_anonymous_message - }) - coVerify { appManagerWrapper.updateAwaiting(appInstall) } - verify(exactly = 0) { InstallWorkManager.enqueueWork(any(), any(), any()) } - } finally { - unmockkObject(InstallWorkManager) - } - } - @Test - fun `canEnqueue refreshes download urls for non-PWA installs`() = runTest { - val appInstall = AppInstall( - type = foundation.e.apps.data.enums.Type.NATIVE, + fun initAppInstall_computesUpdateFlagAndDelegates() = runTest { + val application = Application( + _id = "123", source = Source.PLAY_STORE, - id = "123", - status = Status.AWAITING, - packageName = "com.example.app" - ) - - val appManagerWrapper = mockk(relaxed = true) - val processor = createProcessorForCanEnqueue(appManagerWrapper) - - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - every { networkStatusChecker.isNetworkAvailable() } returns true - every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + status = Status.UPDATABLE, + name = "Example", + package_name = "com.example.app", + type = Type.NATIVE + ) + val appInstall = AppInstall(id = "123", packageName = "com.example.app") + coEvery { appInstallRequestFactory.create(application) } returns appInstall + coEvery { appManagerWrapper.isFusedDownloadInstalled(appInstall) } returns false + coEvery { + appInstallStartCoordinator.enqueue( + appInstall, + true, + application.isSystemApp + ) + } returns true - val result = processor.canEnqueue(appInstall) + val result = appInstallProcessor.initAppInstall(application) assertTrue(result) - coVerify { applicationRepository.updateFusedDownloadWithDownloadingInfo(Source.PLAY_STORE, appInstall) } - coVerify { appManagerWrapper.addDownload(appInstall) } + coVerify { appInstallRequestFactory.create(application) } + coVerify { appInstallStartCoordinator.enqueue(appInstall, true, application.isSystemApp) } } @Test - fun `canEnqueue returns false when download url refresh throws http error`() = runTest { - val appInstall = AppInstall( - type = foundation.e.apps.data.enums.Type.NATIVE, - source = Source.PLAY_STORE, - id = "123", - status = Status.AWAITING, - packageName = "com.example.app" - ) + fun enqueueFusedDownload_delegatesResult() = runTest { + val appInstall = AppInstall(id = "123", packageName = "com.example.app") + coEvery { appInstallStartCoordinator.enqueue(appInstall, true, true) } returns false - val appManagerWrapper = mockk(relaxed = true) - val processor = createProcessorForCanEnqueue(appManagerWrapper) - - coEvery { - applicationRepository.updateFusedDownloadWithDownloadingInfo(Source.PLAY_STORE, appInstall) - } throws GplayHttpRequestException(403, "forbidden") - - val result = processor.canEnqueue(appInstall) + val result = appInstallProcessor.enqueueFusedDownload(appInstall, true, true) assertEquals(false, result) - assertTrue(appEventDispatcher.events.any { it is AppEvent.UpdateEvent }) - coVerify(exactly = 0) { appManagerWrapper.addDownload(appInstall) } + coVerify { appInstallStartCoordinator.enqueue(appInstall, true, true) } } @Test - fun `canEnqueue keeps going when download url refresh throws illegal state`() = runTest { - val appInstall = AppInstall( - type = foundation.e.apps.data.enums.Type.NATIVE, - source = Source.PLAY_STORE, - id = "123", - status = Status.AWAITING, - packageName = "com.example.app" - ) - - val appManagerWrapper = mockk(relaxed = true) - val processor = createProcessorForCanEnqueue(appManagerWrapper) - + fun processInstall_delegatesResult() = runTest { coEvery { - applicationRepository.updateFusedDownloadWithDownloadingInfo(Source.PLAY_STORE, appInstall) - } throws IllegalStateException("boom") - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - every { networkStatusChecker.isNetworkAvailable() } returns true - every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L - - val result = processor.canEnqueue(appInstall) + appInstallWorkRunner.processInstall("123", false, any()) + } returns Result.success(ResultStatus.OK) - assertTrue(result) - coVerify { appManagerWrapper.addDownload(appInstall) } - } - - @Test - fun `processInstall returns success when internal exception occurs`() = runTest { - val fusedDownload = initTest() - fakeFusedManagerRepository.forceCrash = true - - val result = appInstallProcessor.processInstall(fusedDownload.id, false) { + val result = appInstallProcessor.processInstall("123", false) { // _ignored_ } - assertTrue(result.isSuccess) assertEquals(ResultStatus.OK, result.getOrNull()) + coVerify { appInstallWorkRunner.processInstall("123", false, any()) } } - - @Test - fun `processInstall enters foreground before download starts`() = runTest { - val fusedDownload = initTest() - var statusAtForeground: Status? = null - - appInstallProcessor.processInstall(fusedDownload.id, false) { - statusAtForeground = fusedDownload.status - } - - assertEquals(Status.AWAITING, statusAtForeground) - } - - @Test - fun `processInstall update completion ignores installation issues`() = runTest { - stubUpdateNotificationContext() - every { updatesTracker.hasSuccessfulUpdatedApps() } returns true - every { updatesTracker.successfulUpdatedAppsCount() } returns 1 - - processCompletedUpdate(Status.INSTALLATION_ISSUE) - - verify { updatesTracker.addSuccessfullyUpdatedApp(any()) } - verify { updatesNotificationSender.showNotification("Update", "Updated message") } - verify { updatesTracker.clearSuccessfullyUpdatedApps() } - } - - @Test - fun `processInstall update completion ignores purchase needed`() = runTest { - stubUpdateNotificationContext() - every { updatesTracker.hasSuccessfulUpdatedApps() } returns true - every { updatesTracker.successfulUpdatedAppsCount() } returns 1 - - processCompletedUpdate(Status.PURCHASE_NEEDED) - - verify { updatesTracker.addSuccessfullyUpdatedApp(any()) } - verify { updatesNotificationSender.showNotification("Update", "Updated message") } - verify { updatesTracker.clearSuccessfullyUpdatedApps() } - } - - @Test - fun `processInstall clears tracked updates after final notification`() = runTest { - stubUpdateNotificationContext() - every { updatesTracker.hasSuccessfulUpdatedApps() } returns true - every { updatesTracker.successfulUpdatedAppsCount() } returns 1 - - processCompletedUpdate() - - verify { updatesTracker.clearSuccessfullyUpdatedApps() } - } - - @Test - fun canEnqueue_returnsTrueWhenAllChecksPass() = runTest { - val appInstall = AppInstall( - type = Type.PWA, - id = "123", - status = Status.AWAITING, - downloadURLList = mutableListOf("apk"), - packageName = "com.example.app" - ) - - val appManagerWrapper = mockk(relaxed = true) - val processor = createProcessorForCanEnqueue(appManagerWrapper) - - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - every { networkStatusChecker.isNetworkAvailable() } returns true - every { storageSpaceChecker.spaceMissing(appInstall) } returns 0 - - val result = processor.canEnqueue(appInstall) - - assertTrue(result) - coVerify { appManagerWrapper.addDownload(appInstall) } - } - - @Test - fun canEnqueue_returnsFalseWhenNetworkUnavailable() = runTest { - val appInstall = AppInstall( - type = Type.PWA, - id = "123", - status = Status.AWAITING, - downloadURLList = mutableListOf("apk"), - packageName = "com.example.app" - ) - - val appManagerWrapper = mockk(relaxed = true) - val processor = createProcessorForCanEnqueue(appManagerWrapper) - - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - every { networkStatusChecker.isNetworkAvailable() } returns false - every { storageSpaceChecker.spaceMissing(appInstall) } returns 0 - - val result = processor.canEnqueue(appInstall) - - assertEquals(false, result) - coVerify { appManagerWrapper.installationIssue(appInstall) } - } - - @Test - fun canEnqueue_returnsFalseWhenStorageMissing() = runTest { - val appInstall = AppInstall( - type = Type.PWA, - id = "123", - status = Status.AWAITING, - downloadURLList = mutableListOf("apk"), - packageName = "com.example.app" - ) - - val appManagerWrapper = mockk(relaxed = true) - val processor = createProcessorForCanEnqueue(appManagerWrapper) - - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - every { networkStatusChecker.isNetworkAvailable() } returns true - every { storageSpaceChecker.spaceMissing(appInstall) } returns 100L - - val result = processor.canEnqueue(appInstall) - - assertEquals(false, result) - Mockito.verify(storageNotificationManager).showNotEnoughSpaceNotification(appInstall) - coVerify { appManagerWrapper.installationIssue(appInstall) } - } - - @Test - fun canEnqueue_returnsFalseWhenAddDownloadFails() = runTest { - val appInstall = AppInstall( - type = Type.PWA, - id = "123", - status = Status.AWAITING, - downloadURLList = mutableListOf("apk"), - packageName = "com.example.app" - ) - - val appManagerWrapper = mockk(relaxed = true) - val processor = createProcessorForCanEnqueue(appManagerWrapper) - - coEvery { appManagerWrapper.addDownload(appInstall) } returns false - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - every { networkStatusChecker.isNetworkAvailable() } returns true - every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L - - val result = processor.canEnqueue(appInstall) - - assertEquals(false, result) - coVerify(exactly = 0) { appManagerWrapper.installationIssue(appInstall) } - } - - @Test - fun canEnqueue_returnsFalseWhenAgeLimitInvalid() = runTest { - val appInstall = AppInstall( - type = Type.PWA, - id = "123", - status = Status.AWAITING, - downloadURLList = mutableListOf("apk"), - packageName = "com.example.app" - ) - - val appManagerWrapper = mockk(relaxed = true) - val processor = createProcessorForCanEnqueue(appManagerWrapper) - - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.UNKNOWN, ContentRatingValidity(false))) - every { networkStatusChecker.isNetworkAvailable() } returns true - every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L - - val result = processor.canEnqueue(appInstall) - - assertEquals(false, result) - coVerify { appManagerWrapper.cancelDownload(appInstall) } - } - - @Test - fun enqueueFusedDownload_returnsTrueAndEnqueuesWorkForUpdate() = runTest { - val appInstall = createEnqueueAppInstall() - val appManagerWrapper = mockk(relaxed = true) - val processor = createProcessorForCanEnqueue(appManagerWrapper) - - mockInstallWorkManagerSuccess() - every { networkStatusChecker.isNetworkAvailable() } returns true - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L - - val result = processor.enqueueFusedDownload(appInstall, isAnUpdate = true, isSystemApp = true) - - assertTrue(result) - coVerify { appManagerWrapper.updateAwaiting(appInstall) } - verify(exactly = 1) { InstallWorkManager.enqueueWork(context, appInstall, true) } - } - - @Test - fun enqueueFusedDownload_returnsFalseAndMarksIssueWhenUpdateEnqueueFails() = runTest { - val appInstall = createEnqueueAppInstall() - val appManagerWrapper = mockk(relaxed = true) - val processor = createProcessorForCanEnqueue(appManagerWrapper) - - mockInstallWorkManagerFailure() - every { networkStatusChecker.isNetworkAvailable() } returns true - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L - - val result = processor.enqueueFusedDownload(appInstall, isAnUpdate = true, isSystemApp = true) - - assertEquals(false, result) - coVerify { appManagerWrapper.updateAwaiting(appInstall) } - coVerify { appManagerWrapper.installationIssue(appInstall) } - } - - @Test - fun enqueueFusedDownload_returnsTrueWithoutEnqueueingWorkForRegularInstall() = runTest { - val appInstall = createEnqueueAppInstall() - val appManagerWrapper = mockk(relaxed = true) - val processor = createProcessorForCanEnqueue(appManagerWrapper) - - mockInstallWorkManagerSuccess() - every { networkStatusChecker.isNetworkAvailable() } returns true - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L - - val result = processor.enqueueFusedDownload(appInstall, isAnUpdate = false, isSystemApp = true) - - assertTrue(result) - coVerify { appManagerWrapper.updateAwaiting(appInstall) } - verify(exactly = 0) { InstallWorkManager.enqueueWork(any(), any(), any()) } - } - - @Test - fun enqueueFusedDownload_skipsWorkManagerFailurePathForRegularInstall() = runTest { - val appInstall = createEnqueueAppInstall() - val appManagerWrapper = mockk(relaxed = true) - val processor = createProcessorForCanEnqueue(appManagerWrapper) - - mockInstallWorkManagerFailure() - every { networkStatusChecker.isNetworkAvailable() } returns true - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L - - val result = processor.enqueueFusedDownload(appInstall, isAnUpdate = false, isSystemApp = true) - - assertTrue(result) - verify(exactly = 0) { InstallWorkManager.enqueueWork(any(), any(), any()) } - coVerify(exactly = 0) { appManagerWrapper.installationIssue(appInstall) } - } - - @Test - fun initAppInstall_enqueuesUpdateWorkWhenExplicitFlagIsTrue() = runTest { - val application = createApplication(status = Status.INSTALLED) - val appInstall = createExpectedAppInstall(application) - val appManagerWrapper = mockk(relaxed = true) - val processor = createProcessorForCanEnqueue(appManagerWrapper) - - mockInstallWorkManagerSuccess() - every { networkStatusChecker.isNetworkAvailable() } returns true - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - every { appManagerWrapper.isFusedDownloadInstalled(appInstall) } returns false - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L - - val result = processor.initAppInstall(application, isAnUpdate = true) - - assertTrue(result) - verify(exactly = 1) { InstallWorkManager.enqueueWork(context, appInstall, true) } - } - - @Test - fun initAppInstall_enqueuesUpdateWorkWhenApplicationIsUpdatable() = runTest { - val application = createApplication(status = Status.UPDATABLE) - val appInstall = createExpectedAppInstall(application) - val appManagerWrapper = mockk(relaxed = true) - val processor = createProcessorForCanEnqueue(appManagerWrapper) - - mockInstallWorkManagerSuccess() - every { networkStatusChecker.isNetworkAvailable() } returns true - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - every { appManagerWrapper.isFusedDownloadInstalled(appInstall) } returns false - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L - - val result = processor.initAppInstall(application, isAnUpdate = false) - - assertTrue(result) - verify(exactly = 1) { InstallWorkManager.enqueueWork(context, appInstall, true) } - } - - @Test - fun initAppInstall_enqueuesUpdateWorkWhenAppIsAlreadyInstalled() = runTest { - val application = createApplication(status = Status.INSTALLED) - val appInstall = createExpectedAppInstall(application) - val appManagerWrapper = mockk(relaxed = true) - val processor = createProcessorForCanEnqueue(appManagerWrapper) - - mockInstallWorkManagerSuccess() - every { networkStatusChecker.isNetworkAvailable() } returns true - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - every { appManagerWrapper.isFusedDownloadInstalled(appInstall) } returns true - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L - - val result = processor.initAppInstall(application, isAnUpdate = false) - - assertTrue(result) - verify(exactly = 1) { InstallWorkManager.enqueueWork(context, appInstall, true) } - } - - @Test - fun initAppInstall_doesNotEnqueueWorkWhenInstallIsNotAnUpdate() = runTest { - val application = createApplication(status = Status.INSTALLED) - val appInstall = createExpectedAppInstall(application) - val appManagerWrapper = mockk(relaxed = true) - val processor = createProcessorForCanEnqueue(appManagerWrapper) - - mockInstallWorkManagerFailure() - every { networkStatusChecker.isNetworkAvailable() } returns true - Mockito.`when`(validateAppAgeRatingUseCase(appInstall)) - .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) - every { appManagerWrapper.isFusedDownloadInstalled(appInstall) } returns false - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L - - val result = processor.initAppInstall(application, isAnUpdate = false) - - assertTrue(result) - verify(exactly = 0) { InstallWorkManager.enqueueWork(any(), any(), any()) } - } - - private suspend fun runProcessInstall(appInstall: AppInstall): AppInstall? { - appInstallProcessor.processInstall(appInstall.id, false) { - // _ignored_ - } - return fakeFusedDownloadDAO.getDownloadById(appInstall.id) - } - - private suspend fun processCompletedUpdate(ignoredStatus: Status? = null) { - val fusedDownload = initTest() - fakeFusedManagerRepository.isAppInstalled = true - - ignoredStatus?.let { - fakeFusedDownloadDAO.addDownload( - AppInstall( - id = "ignored-$it", - status = it, - downloadURLList = mutableListOf("apk"), - packageName = "com.example.$it" - ) - ) - } - - appInstallProcessor.processInstall(fusedDownload.id, true) { - // _ignored_ - } - } - - private suspend fun stubUpdateNotificationContext() { - val authData = AuthData(email = "user@example.com", isAnonymous = false).apply { - locale = Locale.US - } - coEvery { playStoreAuthStore.awaitAuthData() } returns authData - every { context.getString(R.string.update) } returns "Update" - every { - context.getString( - R.string.message_last_update_triggered, - any(), - any() - ) - } returns "Updated message" - } - - private fun createFusedDownload( - packageName: String? = null, - downloadUrlList: MutableList? = null - ) = AppInstall( - id = "121", - status = Status.AWAITING, - downloadURLList = downloadUrlList ?: mutableListOf("apk1", "apk2"), - packageName = packageName ?: "com.unit.test" - ) - - private fun createProcessorForCanEnqueue( - appManagerWrapper: AppManagerWrapper - ): AppInstallProcessor { - val appInstallRepository = AppInstallRepository(FakeAppInstallDAO()) - val appInstallComponents = AppInstallComponents(appInstallRepository, appManagerWrapper) - val ageLimitGate = AppInstallAgeLimitGate( - validateAppAgeRatingUseCase, - appManagerWrapper, - appEventDispatcher, - parentalControlAuthGateway - ) - val startCoordinator = AppInstallStartCoordinator( - context, - appManagerWrapper, - applicationRepository, - sessionRepository, - playStoreAuthStore, - storageNotificationManager, - ageLimitGate, - appEventDispatcher, - storageSpaceChecker, - networkStatusChecker, - fakeFusedManager - ) - val workRunner = AppInstallWorkRunner( - appInstallRepository, - appManagerWrapper, - mockk(relaxed = true), - appUpdateCompletionHandler - ) - return AppInstallProcessor( - appInstallComponents, - startCoordinator, - workRunner, - appInstallRequestFactory - ) - } - - private fun createEnqueueAppInstall() = AppInstall( - id = "123", - status = Status.AWAITING, - downloadURLList = mutableListOf("https://example.org/app.apk"), - packageName = "com.example.app", - type = Type.PWA, - source = Source.PWA - ) - - private fun createApplication(status: Status) = Application( - _id = "123", - name = "Test app", - package_name = "com.example.app", - status = status, - source = Source.PWA, - type = Type.PWA, - latest_version_code = 1L, - isFree = true, - isSystemApp = true, - url = "https://example.org/app.apk" - ) - - private fun createExpectedAppInstall(application: Application) = AppInstall( - application._id, - application.source, - application.status, - application.name, - application.package_name, - mutableListOf(), - mutableMapOf(), - application.status, - application.type, - application.icon_image_path, - application.latest_version_code, - application.offer_type, - application.isFree, - application.originalSize - ).also { - it.contentRating = application.contentRating - if (it.type == Type.PWA || application.source == Source.SYSTEM_APP) { - it.downloadURLList = mutableListOf(application.url) - } - } - - private fun mockInstallWorkManagerSuccess() { - mockkObject(InstallWorkManager) - isInstallWorkManagerMocked = true - every { InstallWorkManager.getUniqueWorkName(any()) } answers { callOriginal() } - every { InstallWorkManager.enqueueWork(any(), any(), any()) } returns successfulOperation() - } - - private fun mockInstallWorkManagerFailure() { - mockkObject(InstallWorkManager) - isInstallWorkManagerMocked = true - every { InstallWorkManager.getUniqueWorkName(any()) } answers { callOriginal() } - every { InstallWorkManager.enqueueWork(any(), any(), any()) } throws RuntimeException("enqueue failed") - } - - private fun successfulOperation(): Operation { - val operation = mock() - whenever(operation.result).thenReturn(Futures.immediateFuture(Operation.SUCCESS)) - return operation - } - } -- GitLab From 7c80aeb545fb722bb265c6506542ec4ca963345e Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Fri, 13 Mar 2026 01:43:58 +0600 Subject: [PATCH 10/43] refactor: resolve detekt complaints --- .../workmanager/AppInstallAgeLimitGate.kt | 43 +++-- .../AppInstallDevicePreconditions.kt | 63 +++++++ .../AppInstallDownloadUrlRefresher.kt | 111 ++++++++++++ .../AppInstallPreEnqueueChecker.kt | 51 ++++++ .../workmanager/AppInstallProcessor.kt | 1 - .../workmanager/AppInstallStartCoordinator.kt | 164 ++++-------------- .../workmanager/AppInstallWorkRunner.kt | 62 ++++--- .../AppInstallStartCoordinatorTest.kt | 44 +++-- 8 files changed, 357 insertions(+), 182 deletions(-) create mode 100644 app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallDevicePreconditions.kt create mode 100644 app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallDownloadUrlRefresher.kt create mode 100644 app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallPreEnqueueChecker.kt diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallAgeLimitGate.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallAgeLimitGate.kt index 56cabf5fe..c98d5e1e1 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallAgeLimitGate.kt +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallAgeLimitGate.kt @@ -19,6 +19,7 @@ package foundation.e.apps.data.install.workmanager import foundation.e.apps.R +import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall @@ -29,7 +30,6 @@ import foundation.e.apps.domain.model.ContentRatingValidity import kotlinx.coroutines.CompletableDeferred import javax.inject.Inject -@Suppress("ReturnCount") // FIXME: Remove suppression and fix detekt class AppInstallAgeLimitGate @Inject constructor( private val validateAppAgeLimitUseCase: ValidateAppAgeLimitUseCase, private val appManagerWrapper: AppManagerWrapper, @@ -38,28 +38,37 @@ class AppInstallAgeLimitGate @Inject constructor( ) { suspend fun allow(appInstall: AppInstall): Boolean { val ageLimitValidationResult = validateAppAgeLimitUseCase(appInstall) - if (ageLimitValidationResult.data?.isValid == true) { - return true - } + val isAllowed = when { + ageLimitValidationResult.data?.isValid == true -> true + ageLimitValidationResult.isSuccess() -> handleSuccessfulValidation( + ageLimitValidationResult, + appInstall.name + ) - if (ageLimitValidationResult.isSuccess()) { - awaitInvokeAgeLimitEvent(appInstall.name) - if (ageLimitValidationResult.data?.requestPin == true) { - val isAuthenticated = parentalControlAuthGateway.awaitAuthentication() - if (isAuthenticated) { - ageLimitValidationResult.setData(ContentRatingValidity(true)) - } + else -> { + appEventDispatcher.dispatch(AppEvent.ErrorMessageDialogEvent(R.string.data_load_error_desc)) + false } - } else { - appEventDispatcher.dispatch(AppEvent.ErrorMessageDialogEvent(R.string.data_load_error_desc)) } - if (ageLimitValidationResult.data?.isValid == true) { - return true + if (!isAllowed) { + appManagerWrapper.cancelDownload(appInstall) } - appManagerWrapper.cancelDownload(appInstall) - return false + return isAllowed + } + + private suspend fun handleSuccessfulValidation( + ageLimitValidationResult: ResultSupreme, + appName: String + ): Boolean { + awaitInvokeAgeLimitEvent(appName) + if (ageLimitValidationResult.data?.requestPin == true && + parentalControlAuthGateway.awaitAuthentication() + ) { + ageLimitValidationResult.setData(ContentRatingValidity(true)) + } + return ageLimitValidationResult.data?.isValid == true } private suspend fun awaitInvokeAgeLimitEvent(type: String) { diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallDevicePreconditions.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallDevicePreconditions.kt new file mode 100644 index 000000000..e43499ad4 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallDevicePreconditions.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.data.install.workmanager + +import foundation.e.apps.R +import foundation.e.apps.data.event.AppEvent +import foundation.e.apps.data.install.AppManagerWrapper +import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.install.notification.StorageNotificationManager +import foundation.e.apps.data.install.wrapper.AppEventDispatcher +import foundation.e.apps.data.install.wrapper.NetworkStatusChecker +import foundation.e.apps.data.install.wrapper.StorageSpaceChecker +import timber.log.Timber +import javax.inject.Inject + +class AppInstallDevicePreconditions @Inject constructor( + private val appManagerWrapper: AppManagerWrapper, + private val appEventDispatcher: AppEventDispatcher, + private val storageNotificationManager: StorageNotificationManager, + private val storageSpaceChecker: StorageSpaceChecker, + private val networkStatusChecker: NetworkStatusChecker, +) { + suspend fun canProceed(appInstall: AppInstall): Boolean { + val hasNetwork = hasNetworkConnection(appInstall) + return hasNetwork && hasStorageSpace(appInstall) + } + + private suspend fun hasNetworkConnection(appInstall: AppInstall): Boolean { + val hasNetwork = networkStatusChecker.isNetworkAvailable() + if (!hasNetwork) { + appManagerWrapper.installationIssue(appInstall) + appEventDispatcher.dispatch(AppEvent.NoInternetEvent(false)) + } + return hasNetwork + } + + private suspend fun hasStorageSpace(appInstall: AppInstall): Boolean { + val missingStorage = storageSpaceChecker.spaceMissing(appInstall) + if (missingStorage > 0) { + Timber.d("Storage is not available for: ${appInstall.name} size: ${appInstall.appSize}") + storageNotificationManager.showNotEnoughSpaceNotification(appInstall) + appManagerWrapper.installationIssue(appInstall) + appEventDispatcher.dispatch(AppEvent.ErrorMessageEvent(R.string.not_enough_storage)) + } + return missingStorage <= 0 + } +} diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallDownloadUrlRefresher.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallDownloadUrlRefresher.kt new file mode 100644 index 000000000..281eb4022 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallDownloadUrlRefresher.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.data.install.workmanager + +import com.aurora.gplayapi.exceptions.InternalException +import foundation.e.apps.data.ResultSupreme +import foundation.e.apps.data.application.ApplicationRepository +import foundation.e.apps.data.enums.ResultStatus +import foundation.e.apps.data.event.AppEvent +import foundation.e.apps.data.install.AppManager +import foundation.e.apps.data.install.AppManagerWrapper +import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.install.wrapper.AppEventDispatcher +import foundation.e.apps.data.playstore.utils.GplayHttpRequestException +import kotlinx.coroutines.CancellationException +import timber.log.Timber +import javax.inject.Inject + +class AppInstallDownloadUrlRefresher @Inject constructor( + private val applicationRepository: ApplicationRepository, + private val appManagerWrapper: AppManagerWrapper, + private val appEventDispatcher: AppEventDispatcher, + private val appManager: AppManager, +) { + suspend fun updateDownloadUrls(appInstall: AppInstall): Boolean { + return runCatching { + applicationRepository.updateFusedDownloadWithDownloadingInfo( + appInstall.source, + appInstall + ) + }.fold( + onSuccess = { true }, + onFailure = { throwable -> handleUpdateDownloadFailure(appInstall, throwable) } + ) + } + + private suspend fun handleUpdateDownloadFailure(appInstall: AppInstall, throwable: Throwable): Boolean { + return when (throwable) { + is CancellationException -> throw throwable + is InternalException.AppNotPurchased -> handleAppNotPurchased(appInstall) + is GplayHttpRequestException -> { + handleUpdateDownloadError( + appInstall, + "${appInstall.packageName} code: ${throwable.status} exception: ${throwable.localizedMessage}", + throwable + ) + false + } + + is IllegalStateException -> { + Timber.e(throwable) + false + } + + is Exception -> { + handleUpdateDownloadError( + appInstall, + "${appInstall.packageName} exception: ${throwable.localizedMessage}", + throwable + ) + false + } + + else -> throw throwable + } + } + + private suspend fun handleAppNotPurchased(appInstall: AppInstall): Boolean { + if (appInstall.isFree) { + appEventDispatcher.dispatch(AppEvent.AppRestrictedOrUnavailable(appInstall)) + appManager.addDownload(appInstall) + appManager.updateUnavailable(appInstall) + } else { + appManagerWrapper.addFusedDownloadPurchaseNeeded(appInstall) + appEventDispatcher.dispatch(AppEvent.AppPurchaseEvent(appInstall)) + } + return false + } + + private suspend fun handleUpdateDownloadError( + appInstall: AppInstall, + message: String, + exception: Exception + ) { + Timber.e(exception, "Updating download Urls failed for $message") + appEventDispatcher.dispatch( + AppEvent.UpdateEvent( + ResultSupreme.WorkError( + ResultStatus.UNKNOWN, + appInstall + ) + ) + ) + } +} diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallPreEnqueueChecker.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallPreEnqueueChecker.kt new file mode 100644 index 000000000..e1b380350 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallPreEnqueueChecker.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.data.install.workmanager + +import foundation.e.apps.data.enums.Type +import foundation.e.apps.data.install.AppManagerWrapper +import foundation.e.apps.data.install.models.AppInstall +import timber.log.Timber +import javax.inject.Inject + +class AppInstallPreEnqueueChecker @Inject constructor( + private val appInstallDownloadUrlRefresher: AppInstallDownloadUrlRefresher, + private val appManagerWrapper: AppManagerWrapper, + private val appInstallAgeLimitGate: AppInstallAgeLimitGate, + private val appInstallDevicePreconditions: AppInstallDevicePreconditions, +) { + suspend fun canEnqueue(appInstall: AppInstall): Boolean { + val hasUpdatedDownloadUrls = appInstall.type == Type.PWA || + appInstallDownloadUrlRefresher.updateDownloadUrls(appInstall) + + val isDownloadAdded = hasUpdatedDownloadUrls && addDownload(appInstall) + val isAgeLimitAllowed = isDownloadAdded && appInstallAgeLimitGate.allow(appInstall) + + return isAgeLimitAllowed && appInstallDevicePreconditions.canProceed(appInstall) + } + + private suspend fun addDownload(appInstall: AppInstall): Boolean { + val isDownloadAdded = appManagerWrapper.addDownload(appInstall) + if (!isDownloadAdded) { + Timber.i("Update adding ABORTED! status") + } + + return isDownloadAdded + } +} 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 3be4b212e..1301363d9 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 @@ -25,7 +25,6 @@ import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.domain.model.install.Status import javax.inject.Inject -@Suppress("LongParameterList") // FIXME: Remove suppression and fix detekt class AppInstallProcessor @Inject constructor( private val appInstallComponents: AppInstallComponents, private val appInstallStartCoordinator: AppInstallStartCoordinator, diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallStartCoordinator.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallStartCoordinator.kt index 7291aa883..1977543cb 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallStartCoordinator.kt +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallStartCoordinator.kt @@ -19,168 +19,78 @@ package foundation.e.apps.data.install.workmanager import android.content.Context -import com.aurora.gplayapi.exceptions.InternalException import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R -import foundation.e.apps.data.ResultSupreme -import foundation.e.apps.data.application.ApplicationRepository -import foundation.e.apps.data.enums.ResultStatus -import foundation.e.apps.data.enums.Type import foundation.e.apps.data.event.AppEvent -import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.install.notification.StorageNotificationManager import foundation.e.apps.data.install.wrapper.AppEventDispatcher -import foundation.e.apps.data.install.wrapper.NetworkStatusChecker -import foundation.e.apps.data.install.wrapper.StorageSpaceChecker -import foundation.e.apps.data.playstore.utils.GplayHttpRequestException import foundation.e.apps.data.preference.PlayStoreAuthStore import foundation.e.apps.domain.model.User import foundation.e.apps.domain.preferences.SessionRepository +import kotlinx.coroutines.CancellationException import timber.log.Timber import javax.inject.Inject -@Suppress("LongParameterList", "TooGenericExceptionCaught", "ReturnCount") // FIXME: Remove suppression and fix detekt class AppInstallStartCoordinator @Inject constructor( @ApplicationContext private val context: Context, + private val appInstallPreEnqueueChecker: AppInstallPreEnqueueChecker, private val appManagerWrapper: AppManagerWrapper, - private val applicationRepository: ApplicationRepository, private val sessionRepository: SessionRepository, private val playStoreAuthStore: PlayStoreAuthStore, - private val storageNotificationManager: StorageNotificationManager, - private val appInstallAgeLimitGate: AppInstallAgeLimitGate, private val appEventDispatcher: AppEventDispatcher, - private val storageSpaceChecker: StorageSpaceChecker, - private val networkStatusChecker: NetworkStatusChecker, - private val appManager: AppManager, ) { suspend fun enqueue( appInstall: AppInstall, isAnUpdate: Boolean = false, isSystemApp: Boolean = false ): Boolean { - val uniqueWorkName = InstallWorkManager.getUniqueWorkName(appInstall.packageName) - - return try { - val user = sessionRepository.awaitUser() - if (!isSystemApp && (user == User.GOOGLE || user == User.ANONYMOUS)) { - val authData = playStoreAuthStore.awaitAuthData() - if (!appInstall.isFree && authData?.isAnonymous == true) { - appEventDispatcher.dispatch( - AppEvent.ErrorMessageEvent(R.string.paid_app_anonymous_message) - ) + return runCatching { + dispatchAnonymousPaidAppWarning(appInstall, isSystemApp) + + val canEnqueue = canEnqueue(appInstall) + if (canEnqueue) { + appManagerWrapper.updateAwaiting(appInstall) + + // Use only for update work for now. For installation work, see InstallOrchestrator#observeDownloads() + if (isAnUpdate) { + val uniqueWorkName = InstallWorkManager.getUniqueWorkName(appInstall.packageName) + InstallWorkManager.enqueueWork(context, appInstall, true) + Timber.d("UPDATE: Successfully enqueued unique work: $uniqueWorkName") } } - if (!canEnqueue(appInstall)) return false - - appManagerWrapper.updateAwaiting(appInstall) - - // Use only for update work for now. For installation work, see InstallOrchestrator#observeDownloads() - if (isAnUpdate) { - InstallWorkManager.enqueueWork(context, appInstall, true) - Timber.d("UPDATE: Successfully enqueued unique work: $uniqueWorkName") + canEnqueue + }.getOrElse { throwable -> + when (throwable) { + is CancellationException -> throw throwable + is Exception -> { + Timber.e( + throwable, + "Enqueuing App install work is failed for ${appInstall.packageName} " + + "exception: ${throwable.localizedMessage}" + ) + appManagerWrapper.installationIssue(appInstall) + false + } + else -> throw throwable } - true - } catch (e: Exception) { - Timber.e( - e, - "Enqueuing App install work is failed for ${appInstall.packageName} exception: ${e.localizedMessage}" - ) - appManagerWrapper.installationIssue(appInstall) - false } } suspend fun canEnqueue(appInstall: AppInstall): Boolean { - if (appInstall.type != Type.PWA && !updateDownloadUrls(appInstall)) { - return false - } - - if (!appManagerWrapper.addDownload(appInstall)) { - Timber.i("Update adding ABORTED! status") - return false - } - - if (!appInstallAgeLimitGate.allow(appInstall)) { - return false - } - - if (!networkStatusChecker.isNetworkAvailable()) { - appManagerWrapper.installationIssue(appInstall) - appEventDispatcher.dispatch(AppEvent.NoInternetEvent(false)) - return false - } - - if (storageSpaceChecker.spaceMissing(appInstall) > 0) { - Timber.d("Storage is not available for: ${appInstall.name} size: ${appInstall.appSize}") - storageNotificationManager.showNotEnoughSpaceNotification(appInstall) - appManagerWrapper.installationIssue(appInstall) - appEventDispatcher.dispatch(AppEvent.ErrorMessageEvent(R.string.not_enough_storage)) - return false - } - - return true + return appInstallPreEnqueueChecker.canEnqueue(appInstall) } - private suspend fun updateDownloadUrls(appInstall: AppInstall): Boolean { - try { - updateFusedDownloadWithAppDownloadLink(appInstall) - } catch (_: InternalException.AppNotPurchased) { - if (appInstall.isFree) { - handleAppRestricted(appInstall) - return false + private suspend fun dispatchAnonymousPaidAppWarning(appInstall: AppInstall, isSystemApp: Boolean) { + val user = sessionRepository.awaitUser() + if (!isSystemApp && (user == User.GOOGLE || user == User.ANONYMOUS)) { + val authData = playStoreAuthStore.awaitAuthData() + if (!appInstall.isFree && authData?.isAnonymous == true) { + appEventDispatcher.dispatch( + AppEvent.ErrorMessageEvent(R.string.paid_app_anonymous_message) + ) } - appManagerWrapper.addFusedDownloadPurchaseNeeded(appInstall) - appEventDispatcher.dispatch(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 - ) - return false } - return true - } - - private suspend fun handleAppRestricted(appInstall: AppInstall) { - appEventDispatcher.dispatch(AppEvent.AppRestrictedOrUnavailable(appInstall)) - appManager.addDownload(appInstall) - appManager.updateUnavailable(appInstall) - } - - private suspend fun handleUpdateDownloadError( - appInstall: AppInstall, - message: String, - e: Exception - ) { - Timber.e(e, "Updating download Urls failed for $message") - appEventDispatcher.dispatch( - AppEvent.UpdateEvent( - ResultSupreme.WorkError( - ResultStatus.UNKNOWN, - appInstall - ) - ) - ) - } - - private suspend fun updateFusedDownloadWithAppDownloadLink(appInstall: AppInstall) { - applicationRepository.updateFusedDownloadWithDownloadingInfo( - appInstall.source, - appInstall - ) } } diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallWorkRunner.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallWorkRunner.kt index ac9792e65..04e77985d 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallWorkRunner.kt +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallWorkRunner.kt @@ -24,12 +24,12 @@ import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.download.DownloadManagerUtils import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.domain.model.install.Status +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.flow.transformWhile import timber.log.Timber import javax.inject.Inject -@Suppress("TooGenericExceptionCaught") // FIXME: Remove suppression and fix detekt class AppInstallWorkRunner @Inject constructor( private val appInstallRepository: AppInstallRepository, private val appManagerWrapper: AppManagerWrapper, @@ -43,45 +43,53 @@ class AppInstallWorkRunner @Inject constructor( runInForeground: suspend (String) -> Unit ): Result { var appInstall: AppInstall? = null - try { + runCatching { Timber.d("Fused download name $fusedDownloadId") appInstall = appInstallRepository.getDownloadById(fusedDownloadId) Timber.i(">>> doWork started for Fused download name ${appInstall?.name} $fusedDownloadId") appInstall?.let { - checkDownloadingState(appInstall) + checkDownloadingState(it) val isUpdateWork = - isItUpdateWork && appManagerWrapper.isFusedDownloadInstalled(appInstall) + isItUpdateWork && appManagerWrapper.isFusedDownloadInstalled(it) - if (!appInstall.isAppInstalling()) { + if (!it.isAppInstalling()) { Timber.d("!!! returned") return@let } - if (!appManagerWrapper.validateFusedDownload(appInstall)) { + if (!appManagerWrapper.validateFusedDownload(it)) { appManagerWrapper.installationIssue(it) Timber.d("!!! installationIssue") return@let } - if (areFilesDownloadedButNotInstalled(appInstall)) { - Timber.i("===> Downloaded But not installed ${appInstall.name}") - appManagerWrapper.updateDownloadStatus(appInstall, Status.INSTALLING) + if (areFilesDownloadedButNotInstalled(it)) { + Timber.i("===> Downloaded But not installed ${it.name}") + appManagerWrapper.updateDownloadStatus(it, Status.INSTALLING) } runInForeground.invoke(it.name) startAppInstallationProcess(it, isUpdateWork) } - } catch (e: Exception) { - Timber.e( - e, - "Install worker is failed for ${appInstall?.packageName} exception: ${e.localizedMessage}" - ) - appInstall?.let { - appManagerWrapper.cancelDownload(appInstall) + }.onFailure { throwable -> + when (throwable) { + is CancellationException -> throw throwable + is Exception -> { + Timber.e( + throwable, + "Install worker is failed for ${appInstall?.packageName} " + + "exception: ${throwable.localizedMessage}" + ) + appInstall?.let { + appManagerWrapper.cancelDownload(it) + } + } + + else -> throw throwable } } @@ -138,14 +146,22 @@ class AppInstallWorkRunner @Inject constructor( download: AppInstall, isUpdateWork: Boolean ) { - try { + runCatching { handleFusedDownloadStatus(download, isUpdateWork) - } catch (e: Exception) { - val message = - "Handling install status is failed for ${download.packageName} exception: ${e.localizedMessage}" - Timber.e(e, message) - appManagerWrapper.installationIssue(download) - finishInstallation(download, isUpdateWork) + }.onFailure { throwable -> + when (throwable) { + is CancellationException -> throw throwable + is Exception -> { + val message = + "Handling install status is failed for ${download.packageName} " + + "exception: ${throwable.localizedMessage}" + Timber.e(throwable, message) + appManagerWrapper.installationIssue(download) + finishInstallation(download, isUpdateWork) + } + + else -> throw throwable + } } } diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt index 2385a90a2..015f6470a 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt @@ -31,6 +31,9 @@ import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager import foundation.e.apps.data.install.workmanager.AppInstallAgeLimitGate +import foundation.e.apps.data.install.workmanager.AppInstallDevicePreconditions +import foundation.e.apps.data.install.workmanager.AppInstallDownloadUrlRefresher +import foundation.e.apps.data.install.workmanager.AppInstallPreEnqueueChecker import foundation.e.apps.data.install.workmanager.AppInstallStartCoordinator import foundation.e.apps.data.install.workmanager.InstallWorkManager import foundation.e.apps.data.install.wrapper.NetworkStatusChecker @@ -68,6 +71,9 @@ class AppInstallStartCoordinatorTest { private lateinit var storageSpaceChecker: StorageSpaceChecker private lateinit var networkStatusChecker: NetworkStatusChecker private lateinit var appManager: AppManager + private lateinit var devicePreconditions: AppInstallDevicePreconditions + private lateinit var downloadUrlRefresher: AppInstallDownloadUrlRefresher + private lateinit var preflightChecker: AppInstallPreEnqueueChecker private lateinit var coordinator: AppInstallStartCoordinator @Before @@ -85,18 +91,32 @@ class AppInstallStartCoordinatorTest { appManager = mockk(relaxed = true) coEvery { sessionRepository.awaitUser() } returns User.NO_GOOGLE coEvery { playStoreAuthStore.awaitAuthData() } returns null + downloadUrlRefresher = AppInstallDownloadUrlRefresher( + applicationRepository, + appManagerWrapper, + appEventDispatcher, + appManager + ) + devicePreconditions = AppInstallDevicePreconditions( + appManagerWrapper, + appEventDispatcher, + storageNotificationManager, + storageSpaceChecker, + networkStatusChecker + ) + preflightChecker = AppInstallPreEnqueueChecker( + downloadUrlRefresher, + appManagerWrapper, + appInstallAgeLimitGate, + devicePreconditions + ) coordinator = AppInstallStartCoordinator( context, + preflightChecker, appManagerWrapper, - applicationRepository, sessionRepository, playStoreAuthStore, - storageNotificationManager, - appInstallAgeLimitGate, - appEventDispatcher, - storageSpaceChecker, - networkStatusChecker, - appManager + appEventDispatcher ) } @@ -242,7 +262,7 @@ class AppInstallStartCoordinatorTest { } @Test - fun canEnqueue_keepsGoingWhenDownloadUrlRefreshThrowsIllegalState() = runTest { + fun canEnqueue_returnsFalseWhenDownloadUrlRefreshThrowsIllegalState() = runTest { val appInstall = createNativeInstall() coEvery { @@ -251,15 +271,11 @@ class AppInstallStartCoordinatorTest { appInstall ) } throws IllegalStateException("boom") - coEvery { appManagerWrapper.addDownload(appInstall) } returns true - coEvery { appInstallAgeLimitGate.allow(appInstall) } returns true - every { networkStatusChecker.isNetworkAvailable() } returns true - every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L val result = coordinator.canEnqueue(appInstall) - assertTrue(result) - coVerify { appManagerWrapper.addDownload(appInstall) } + assertFalse(result) + coVerify(exactly = 0) { appManagerWrapper.addDownload(appInstall) } } private fun createPwaInstall(isFree: Boolean = true) = AppInstall( -- GitLab From 533c39764f18951b17f4cee81d48c4e71fe12ad6 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Wed, 18 Mar 2026 00:41:51 +0600 Subject: [PATCH 11/43] refactor: simplify install flow code Flatten processInstall into a guard-clause flow and add a local ReturnCount suppression so the install preconditions and execution path are easier to read. This keeps the existing install behavior and failure handling intact while reducing nesting and temporary state. --- .../AppInstallDownloadUrlRefresher.kt | 72 +++++++++-------- .../AppInstallPreEnqueueChecker.kt | 4 +- .../workmanager/AppInstallStartCoordinator.kt | 8 +- .../workmanager/AppInstallWorkRunner.kt | 80 +++++++++---------- .../install/workmanager/InstallAppWorker.kt | 17 ++-- .../AppInstallStartCoordinatorTest.kt | 31 +++++++ .../AppInstallWorkRunnerTest.kt | 30 ++++++- 7 files changed, 152 insertions(+), 90 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallDownloadUrlRefresher.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallDownloadUrlRefresher.kt index 281eb4022..bc4ac8c6a 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallDownloadUrlRefresher.kt +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallDownloadUrlRefresher.kt @@ -23,6 +23,7 @@ import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.application.ApplicationRepository import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.event.AppEvent +import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall @@ -34,11 +35,12 @@ import javax.inject.Inject class AppInstallDownloadUrlRefresher @Inject constructor( private val applicationRepository: ApplicationRepository, + private val appInstallRepository: AppInstallRepository, private val appManagerWrapper: AppManagerWrapper, private val appEventDispatcher: AppEventDispatcher, private val appManager: AppManager, ) { - suspend fun updateDownloadUrls(appInstall: AppInstall): Boolean { + suspend fun updateDownloadUrls(appInstall: AppInstall, isAnUpdate: Boolean): Boolean { return runCatching { applicationRepository.updateFusedDownloadWithDownloadingInfo( appInstall.source, @@ -46,34 +48,32 @@ class AppInstallDownloadUrlRefresher @Inject constructor( ) }.fold( onSuccess = { true }, - onFailure = { throwable -> handleUpdateDownloadFailure(appInstall, throwable) } + onFailure = { throwable -> + handleUpdateDownloadFailure( + appInstall, + isAnUpdate, + throwable + ) + } ) } - private suspend fun handleUpdateDownloadFailure(appInstall: AppInstall, throwable: Throwable): Boolean { + private suspend fun handleUpdateDownloadFailure( + appInstall: AppInstall, + isAnUpdate: Boolean, + throwable: Throwable + ): Boolean { return when (throwable) { is CancellationException -> throw throwable is InternalException.AppNotPurchased -> handleAppNotPurchased(appInstall) - is GplayHttpRequestException -> { - handleUpdateDownloadError( - appInstall, - "${appInstall.packageName} code: ${throwable.status} exception: ${throwable.localizedMessage}", - throwable - ) - false - } - - is IllegalStateException -> { - Timber.e(throwable) - false - } - is Exception -> { - handleUpdateDownloadError( - appInstall, - "${appInstall.packageName} exception: ${throwable.localizedMessage}", - throwable - ) + val message = if (throwable is GplayHttpRequestException) { + "${appInstall.packageName} code: ${throwable.status} exception: ${throwable.message}" + } else { + "${appInstall.packageName} exception: ${throwable.message}" + } + Timber.e(throwable, "Updating download URLS failed for $message") + handleUpdateDownloadError(appInstall, isAnUpdate) false } @@ -93,19 +93,23 @@ class AppInstallDownloadUrlRefresher @Inject constructor( return false } - private suspend fun handleUpdateDownloadError( - appInstall: AppInstall, - message: String, - exception: Exception - ) { - Timber.e(exception, "Updating download Urls failed for $message") - appEventDispatcher.dispatch( - AppEvent.UpdateEvent( - ResultSupreme.WorkError( - ResultStatus.UNKNOWN, - appInstall + private suspend fun handleUpdateDownloadError(appInstall: AppInstall, isAnUpdate: Boolean) { + // Insert into DB to reflect error state on UI. + // For example, install button's label will change to Install -> Cancel -> Retry + if (appInstallRepository.getDownloadById(appInstall.id) == null) { + appInstallRepository.addDownload(appInstall) + } + appManagerWrapper.installationIssue(appInstall) + + if (isAnUpdate) { + appEventDispatcher.dispatch( + AppEvent.UpdateEvent( + ResultSupreme.WorkError( + ResultStatus.UNKNOWN, + appInstall + ) ) ) - ) + } } } diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallPreEnqueueChecker.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallPreEnqueueChecker.kt index e1b380350..04684a195 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallPreEnqueueChecker.kt +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallPreEnqueueChecker.kt @@ -30,9 +30,9 @@ class AppInstallPreEnqueueChecker @Inject constructor( private val appInstallAgeLimitGate: AppInstallAgeLimitGate, private val appInstallDevicePreconditions: AppInstallDevicePreconditions, ) { - suspend fun canEnqueue(appInstall: AppInstall): Boolean { + suspend fun canEnqueue(appInstall: AppInstall, isAnUpdate: Boolean = false): Boolean { val hasUpdatedDownloadUrls = appInstall.type == Type.PWA || - appInstallDownloadUrlRefresher.updateDownloadUrls(appInstall) + appInstallDownloadUrlRefresher.updateDownloadUrls(appInstall, isAnUpdate) val isDownloadAdded = hasUpdatedDownloadUrls && addDownload(appInstall) val isAgeLimitAllowed = isDownloadAdded && appInstallAgeLimitGate.allow(appInstall) diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallStartCoordinator.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallStartCoordinator.kt index 1977543cb..c0e11f43e 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallStartCoordinator.kt +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallStartCoordinator.kt @@ -48,7 +48,7 @@ class AppInstallStartCoordinator @Inject constructor( return runCatching { dispatchAnonymousPaidAppWarning(appInstall, isSystemApp) - val canEnqueue = canEnqueue(appInstall) + val canEnqueue = canEnqueue(appInstall, isAnUpdate) if (canEnqueue) { appManagerWrapper.updateAwaiting(appInstall) @@ -58,6 +58,8 @@ class AppInstallStartCoordinator @Inject constructor( InstallWorkManager.enqueueWork(context, appInstall, true) Timber.d("UPDATE: Successfully enqueued unique work: $uniqueWorkName") } + } else { + Timber.w("Can't enqueue ${appInstall.name}/${appInstall.packageName} for installation.") } canEnqueue @@ -78,8 +80,8 @@ class AppInstallStartCoordinator @Inject constructor( } } - suspend fun canEnqueue(appInstall: AppInstall): Boolean { - return appInstallPreEnqueueChecker.canEnqueue(appInstall) + suspend fun canEnqueue(appInstall: AppInstall, isAnUpdate: Boolean = false): Boolean { + return appInstallPreEnqueueChecker.canEnqueue(appInstall, isAnUpdate) } private suspend fun dispatchAnonymousPaidAppWarning(appInstall: AppInstall, isSystemApp: Boolean) { diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallWorkRunner.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallWorkRunner.kt index 04e77985d..ab59f1e12 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallWorkRunner.kt +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallWorkRunner.kt @@ -36,65 +36,63 @@ class AppInstallWorkRunner @Inject constructor( private val downloadManager: DownloadManagerUtils, private val appUpdateCompletionHandler: AppUpdateCompletionHandler, ) { + @Suppress("ReturnCount") @OptIn(DelicateCoroutinesApi::class) suspend fun processInstall( fusedDownloadId: String, isItUpdateWork: Boolean, runInForeground: suspend (String) -> Unit ): Result { - var appInstall: AppInstall? = null - runCatching { - Timber.d("Fused download name $fusedDownloadId") + val appInstall = + appInstallRepository.getDownloadById(fusedDownloadId) ?: return Result.failure( + IllegalStateException("App can't be null here.") + ) - appInstall = appInstallRepository.getDownloadById(fusedDownloadId) - Timber.i(">>> doWork started for Fused download name ${appInstall?.name} $fusedDownloadId") + Timber.i(">>> doWork() started for ${appInstall.name}/${appInstall.packageName}") - appInstall?.let { - checkDownloadingState(it) + checkDownloadingState(appInstall) - val isUpdateWork = - isItUpdateWork && appManagerWrapper.isFusedDownloadInstalled(it) + if (!appInstall.isAppInstalling()) { + val message = "${appInstall.status} is in invalid state" + Timber.w(message) - if (!it.isAppInstalling()) { - Timber.d("!!! returned") - return@let - } + return Result.failure(IllegalStateException(message)) + } - if (!appManagerWrapper.validateFusedDownload(it)) { - appManagerWrapper.installationIssue(it) - Timber.d("!!! installationIssue") - return@let - } + if (!appManagerWrapper.validateFusedDownload(appInstall)) { + appManagerWrapper.installationIssue(appInstall) + val message = "Installation issue for ${appInstall.name}/${appInstall.packageName}" + Timber.w(message) - if (areFilesDownloadedButNotInstalled(it)) { - Timber.i("===> Downloaded But not installed ${it.name}") - appManagerWrapper.updateDownloadStatus(it, Status.INSTALLING) - } + return Result.failure(IllegalStateException(message)) + } - runInForeground.invoke(it.name) + return runCatching { + val isUpdateWork = + isItUpdateWork && appManagerWrapper.isFusedDownloadInstalled(appInstall) - startAppInstallationProcess(it, isUpdateWork) + if (areFilesDownloadedButNotInstalled(appInstall)) { + Timber.i("===> Downloaded But not installed ${appInstall.name}") + appManagerWrapper.updateDownloadStatus(appInstall, Status.INSTALLING) } - }.onFailure { throwable -> - when (throwable) { - is CancellationException -> throw throwable - is Exception -> { - Timber.e( - throwable, - "Install worker is failed for ${appInstall?.packageName} " + - "exception: ${throwable.localizedMessage}" - ) - appInstall?.let { - appManagerWrapper.cancelDownload(it) - } - } - else -> throw throwable + runInForeground.invoke(appInstall.name) + startAppInstallationProcess(appInstall, isUpdateWork) + Timber.i("doWork: RESULT SUCCESS: ${appInstall.name}") + + ResultStatus.OK + }.onFailure { exception -> + if (exception is CancellationException) { + throw exception } - } - Timber.i("doWork: RESULT SUCCESS: ${appInstall?.name}") - return Result.success(ResultStatus.OK) + Timber.e( + exception, + "Install worker failed for ${appInstall.packageName} exception: ${exception.message}" + ) + + appManagerWrapper.cancelDownload(appInstall) + } } @OptIn(DelicateCoroutinesApi::class) 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 { diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt index 015f6470a..9975a676a 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt @@ -27,6 +27,7 @@ import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Type import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppManager +import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager @@ -63,6 +64,7 @@ class AppInstallStartCoordinatorTest { private lateinit var context: Context private lateinit var appManagerWrapper: AppManagerWrapper private lateinit var applicationRepository: ApplicationRepository + private lateinit var appInstallRepository: AppInstallRepository private lateinit var sessionRepository: SessionRepository private lateinit var playStoreAuthStore: PlayStoreAuthStore private lateinit var storageNotificationManager: StorageNotificationManager @@ -81,6 +83,7 @@ class AppInstallStartCoordinatorTest { context = mockk(relaxed = true) appManagerWrapper = mockk(relaxed = true) applicationRepository = mockk(relaxed = true) + appInstallRepository = mockk(relaxed = true) sessionRepository = mockk(relaxed = true) playStoreAuthStore = mockk(relaxed = true) storageNotificationManager = mockk(relaxed = true) @@ -93,6 +96,7 @@ class AppInstallStartCoordinatorTest { coEvery { playStoreAuthStore.awaitAuthData() } returns null downloadUrlRefresher = AppInstallDownloadUrlRefresher( applicationRepository, + appInstallRepository, appManagerWrapper, appEventDispatcher, appManager @@ -247,6 +251,7 @@ class AppInstallStartCoordinatorTest { fun canEnqueue_returnsFalseWhenDownloadUrlRefreshThrowsHttpError() = runTest { val appInstall = createNativeInstall() + coEvery { appInstallRepository.getDownloadById(appInstall.id) } returns null coEvery { applicationRepository.updateFusedDownloadWithDownloadingInfo( Source.PLAY_STORE, @@ -256,8 +261,31 @@ class AppInstallStartCoordinatorTest { val result = coordinator.canEnqueue(appInstall) + assertFalse(result) + assertTrue(appEventDispatcher.events.none { it is AppEvent.UpdateEvent }) + coVerify { appInstallRepository.addDownload(appInstall) } + coVerify { appManagerWrapper.installationIssue(appInstall) } + coVerify(exactly = 0) { appManagerWrapper.addDownload(appInstall) } + } + + @Test + fun canEnqueue_dispatchesUpdateEventWhenDownloadUrlRefreshFailsForUpdate() = runTest { + val appInstall = createNativeInstall() + + coEvery { appInstallRepository.getDownloadById(appInstall.id) } returns null + coEvery { + applicationRepository.updateFusedDownloadWithDownloadingInfo( + Source.PLAY_STORE, + appInstall + ) + } throws GplayHttpRequestException(403, "forbidden") + + val result = coordinator.canEnqueue(appInstall, true) + assertFalse(result) assertTrue(appEventDispatcher.events.any { it is AppEvent.UpdateEvent }) + coVerify { appInstallRepository.addDownload(appInstall) } + coVerify { appManagerWrapper.installationIssue(appInstall) } coVerify(exactly = 0) { appManagerWrapper.addDownload(appInstall) } } @@ -265,6 +293,7 @@ class AppInstallStartCoordinatorTest { fun canEnqueue_returnsFalseWhenDownloadUrlRefreshThrowsIllegalState() = runTest { val appInstall = createNativeInstall() + coEvery { appInstallRepository.getDownloadById(appInstall.id) } returns null coEvery { applicationRepository.updateFusedDownloadWithDownloadingInfo( Source.PLAY_STORE, @@ -275,6 +304,8 @@ class AppInstallStartCoordinatorTest { val result = coordinator.canEnqueue(appInstall) assertFalse(result) + coVerify { appInstallRepository.addDownload(appInstall) } + coVerify { appManagerWrapper.installationIssue(appInstall) } coVerify(exactly = 0) { appManagerWrapper.addDownload(appInstall) } } diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallWorkRunnerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallWorkRunnerTest.kt index 7ac00a528..3e0e3da90 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallWorkRunnerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallWorkRunnerTest.kt @@ -20,7 +20,6 @@ package foundation.e.apps.installProcessor import android.content.Context import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.fdroid.FDroidRepository import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManager @@ -132,7 +131,7 @@ class AppInstallWorkRunnerTest { } @Test - fun processInstall_returnsSuccessWhenInternalExceptionOccurs() = runTest { + fun processInstall_returnsFailureWhenInternalExceptionOccurs() = runTest { val fusedDownload = initTest() fakeFusedManagerRepository.forceCrash = true @@ -141,11 +140,34 @@ class AppInstallWorkRunnerTest { } val finalFusedDownload = fakeFusedDownloadDAO.getDownloadById(fusedDownload.id) - assertTrue(result.isSuccess) - assertEquals(ResultStatus.OK, result.getOrNull()) + assertTrue(result.isFailure) assertTrue(finalFusedDownload == null || fusedDownload.status == Status.INSTALLATION_ISSUE) } + @Test + fun processInstall_returnsFailureWhenStatusIsInvalid() = runTest { + val fusedDownload = initTest() + fusedDownload.status = Status.BLOCKED + + val result = workRunner.processInstall(fusedDownload.id, false) { + // _ignored_ + } + val finalFusedDownload = fakeFusedDownloadDAO.getDownloadById(fusedDownload.id) + + assertTrue(result.isFailure) + assertEquals(Status.BLOCKED, finalFusedDownload?.status) + } + + @Test + fun processInstall_returnsFailureWhenDownloadMissing() = runTest { + val result = workRunner.processInstall("missing", false) { + // _ignored_ + } + + assertTrue(result.isFailure) + assertTrue(result.exceptionOrNull() is IllegalStateException) + } + @Test fun processInstall_reportsDownloadFailure() = runTest { val fusedDownload = initTest() -- GitLab From c2efb77a6866a91d5c636bda29d900f05114107e Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Wed, 18 Mar 2026 01:30:05 +0600 Subject: [PATCH 12/43] test: cover pre-enqueue install gating Pin the new pre-enqueue orchestration logic after the install refactor so short-circuit regressions are caught directly. --- .../AppInstallPreEnqueueCheckerTest.kt | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt new file mode 100644 index 000000000..085af08db --- /dev/null +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.installProcessor + +import foundation.e.apps.data.enums.Source +import foundation.e.apps.data.enums.Type +import foundation.e.apps.data.install.AppManagerWrapper +import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.install.workmanager.AppInstallAgeLimitGate +import foundation.e.apps.data.install.workmanager.AppInstallDevicePreconditions +import foundation.e.apps.data.install.workmanager.AppInstallDownloadUrlRefresher +import foundation.e.apps.data.install.workmanager.AppInstallPreEnqueueChecker +import foundation.e.apps.domain.model.install.Status +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class AppInstallPreEnqueueCheckerTest { + private lateinit var appInstallDownloadUrlRefresher: AppInstallDownloadUrlRefresher + private lateinit var appManagerWrapper: AppManagerWrapper + private lateinit var appInstallAgeLimitGate: AppInstallAgeLimitGate + private lateinit var appInstallDevicePreconditions: AppInstallDevicePreconditions + private lateinit var checker: AppInstallPreEnqueueChecker + + @Before + fun setup() { + appInstallDownloadUrlRefresher = mockk(relaxed = true) + appManagerWrapper = mockk(relaxed = true) + appInstallAgeLimitGate = mockk(relaxed = true) + appInstallDevicePreconditions = mockk(relaxed = true) + checker = AppInstallPreEnqueueChecker( + appInstallDownloadUrlRefresher, + appManagerWrapper, + appInstallAgeLimitGate, + appInstallDevicePreconditions + ) + } + + @Test + fun canEnqueue_skipsDownloadUrlRefreshForPwaInstalls() = runTest { + val appInstall = createPwaInstall() + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + coEvery { appInstallAgeLimitGate.allow(appInstall) } returns true + coEvery { appInstallDevicePreconditions.canProceed(appInstall) } returns true + + val result = checker.canEnqueue(appInstall) + + assertTrue(result) + coVerify(exactly = 0) { + appInstallDownloadUrlRefresher.updateDownloadUrls(any(), any()) + } + } + + @Test + fun canEnqueue_stopsWhenDownloadRefreshFails() = runTest { + val appInstall = createNativeInstall() + coEvery { appInstallDownloadUrlRefresher.updateDownloadUrls(appInstall, false) } returns false + + val result = checker.canEnqueue(appInstall) + + assertFalse(result) + coVerify(exactly = 0) { appManagerWrapper.addDownload(any()) } + coVerify(exactly = 0) { appInstallAgeLimitGate.allow(any()) } + coVerify(exactly = 0) { appInstallDevicePreconditions.canProceed(any()) } + } + + @Test + fun canEnqueue_stopsWhenAddingDownloadFails() = runTest { + val appInstall = createNativeInstall() + coEvery { appInstallDownloadUrlRefresher.updateDownloadUrls(appInstall, false) } returns true + coEvery { appManagerWrapper.addDownload(appInstall) } returns false + + val result = checker.canEnqueue(appInstall) + + assertFalse(result) + coVerify(exactly = 0) { appInstallAgeLimitGate.allow(any()) } + coVerify(exactly = 0) { appInstallDevicePreconditions.canProceed(any()) } + } + + @Test + fun canEnqueue_stopsWhenAgeLimitRejectsInstall() = runTest { + val appInstall = createNativeInstall() + coEvery { appInstallDownloadUrlRefresher.updateDownloadUrls(appInstall, false) } returns true + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + coEvery { appInstallAgeLimitGate.allow(appInstall) } returns false + + val result = checker.canEnqueue(appInstall) + + assertFalse(result) + coVerify(exactly = 0) { appInstallDevicePreconditions.canProceed(any()) } + } + + @Test + fun canEnqueue_returnsTrueWhenAllChecksPass() = runTest { + val appInstall = createNativeInstall() + coEvery { appInstallDownloadUrlRefresher.updateDownloadUrls(appInstall, false) } returns true + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + coEvery { appInstallAgeLimitGate.allow(appInstall) } returns true + coEvery { appInstallDevicePreconditions.canProceed(appInstall) } returns true + + val result = checker.canEnqueue(appInstall) + + assertTrue(result) + } + + private fun createPwaInstall() = AppInstall( + type = Type.PWA, + id = "123", + status = Status.AWAITING, + downloadURLList = mutableListOf("apk"), + packageName = "com.example.app" + ) + + private fun createNativeInstall() = AppInstall( + type = Type.NATIVE, + source = Source.PLAY_STORE, + id = "123", + status = Status.AWAITING, + packageName = "com.example.app" + ) +} -- GitLab From 14a8318b9aabb9a4322074eb8bb4dd2ab5c85fdd Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Wed, 18 Mar 2026 01:33:02 +0600 Subject: [PATCH 13/43] test: cover download refresh failure handling Pin the refresh error branches introduced by the install refactor so purchase and retry flows keep their current side effects. --- .../AppInstallDownloadUrlRefresherTest.kt | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 app/src/test/java/foundation/e/apps/installProcessor/AppInstallDownloadUrlRefresherTest.kt diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallDownloadUrlRefresherTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallDownloadUrlRefresherTest.kt new file mode 100644 index 000000000..6be024abf --- /dev/null +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallDownloadUrlRefresherTest.kt @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.installProcessor + +import com.aurora.gplayapi.exceptions.InternalException +import foundation.e.apps.data.enums.Source +import foundation.e.apps.data.enums.Type +import foundation.e.apps.data.event.AppEvent +import foundation.e.apps.data.application.ApplicationRepository +import foundation.e.apps.data.install.AppInstallRepository +import foundation.e.apps.data.install.AppManager +import foundation.e.apps.data.install.AppManagerWrapper +import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.install.workmanager.AppInstallDownloadUrlRefresher +import foundation.e.apps.data.playstore.utils.GplayHttpRequestException +import foundation.e.apps.domain.model.install.Status +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import kotlin.test.assertFailsWith +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class AppInstallDownloadUrlRefresherTest { + private lateinit var applicationRepository: ApplicationRepository + private lateinit var appInstallRepository: AppInstallRepository + private lateinit var appManagerWrapper: AppManagerWrapper + private lateinit var appEventDispatcher: FakeAppEventDispatcher + private lateinit var appManager: AppManager + private lateinit var refresher: AppInstallDownloadUrlRefresher + + @Before + fun setup() { + applicationRepository = mockk(relaxed = true) + appInstallRepository = mockk(relaxed = true) + appManagerWrapper = mockk(relaxed = true) + appEventDispatcher = FakeAppEventDispatcher() + appManager = mockk(relaxed = true) + refresher = AppInstallDownloadUrlRefresher( + applicationRepository, + appInstallRepository, + appManagerWrapper, + appEventDispatcher, + appManager + ) + } + + @Test + fun updateDownloadUrls_returnsTrueWhenRefreshSucceeds() = runTest { + val appInstall = createNativeInstall() + coEvery { + applicationRepository.updateFusedDownloadWithDownloadingInfo(Source.PLAY_STORE, appInstall) + } returns Unit + + val result = refresher.updateDownloadUrls(appInstall, false) + + assertTrue(result) + coVerify(exactly = 0) { appManagerWrapper.installationIssue(any()) } + } + + @Test + fun updateDownloadUrls_handlesFreeAppNotPurchasedAsRestricted() = runTest { + val appInstall = createNativeInstall(isFree = true) + coEvery { + applicationRepository.updateFusedDownloadWithDownloadingInfo(Source.PLAY_STORE, appInstall) + } throws InternalException.AppNotPurchased() + + val result = refresher.updateDownloadUrls(appInstall, false) + + assertFalse(result) + assertTrue(appEventDispatcher.events.any { it is AppEvent.AppRestrictedOrUnavailable }) + coVerify { appManager.addDownload(appInstall) } + coVerify { appManager.updateUnavailable(appInstall) } + coVerify(exactly = 0) { appManagerWrapper.addFusedDownloadPurchaseNeeded(any()) } + } + + @Test + fun updateDownloadUrls_handlesPaidAppNotPurchasedAsPurchaseNeeded() = runTest { + val appInstall = createNativeInstall(isFree = false) + coEvery { + applicationRepository.updateFusedDownloadWithDownloadingInfo(Source.PLAY_STORE, appInstall) + } throws InternalException.AppNotPurchased() + + val result = refresher.updateDownloadUrls(appInstall, false) + + assertFalse(result) + coVerify { appManagerWrapper.addFusedDownloadPurchaseNeeded(appInstall) } + assertTrue(appEventDispatcher.events.any { it is AppEvent.AppPurchaseEvent }) + coVerify(exactly = 0) { appManager.addDownload(any()) } + } + + @Test + fun updateDownloadUrls_recordsIssueWhenHttpRefreshFails() = runTest { + val appInstall = createNativeInstall() + coEvery { appInstallRepository.getDownloadById(appInstall.id) } returns null + coEvery { + applicationRepository.updateFusedDownloadWithDownloadingInfo(Source.PLAY_STORE, appInstall) + } throws GplayHttpRequestException(403, "forbidden") + + val result = refresher.updateDownloadUrls(appInstall, false) + + assertFalse(result) + coVerify { appInstallRepository.addDownload(appInstall) } + coVerify { appManagerWrapper.installationIssue(appInstall) } + assertTrue(appEventDispatcher.events.none { it is AppEvent.UpdateEvent }) + } + + @Test + fun updateDownloadUrls_dispatchesUpdateEventWhenUpdateRefreshFails() = runTest { + val appInstall = createNativeInstall() + coEvery { appInstallRepository.getDownloadById(appInstall.id) } returns null + coEvery { + applicationRepository.updateFusedDownloadWithDownloadingInfo(Source.PLAY_STORE, appInstall) + } throws IllegalStateException("boom") + + val result = refresher.updateDownloadUrls(appInstall, true) + + assertFalse(result) + coVerify { appInstallRepository.addDownload(appInstall) } + coVerify { appManagerWrapper.installationIssue(appInstall) } + assertTrue(appEventDispatcher.events.any { it is AppEvent.UpdateEvent }) + } + + @Test + fun updateDownloadUrls_doesNotDuplicateExistingDownloadOnFailure() = runTest { + val appInstall = createNativeInstall() + coEvery { appInstallRepository.getDownloadById(appInstall.id) } returns appInstall + coEvery { + applicationRepository.updateFusedDownloadWithDownloadingInfo(Source.PLAY_STORE, appInstall) + } throws IllegalStateException("boom") + + val result = refresher.updateDownloadUrls(appInstall, false) + + assertFalse(result) + coVerify(exactly = 0) { appInstallRepository.addDownload(any()) } + coVerify { appManagerWrapper.installationIssue(appInstall) } + } + + @Test + fun updateDownloadUrls_rethrowsCancellation() = runTest { + val appInstall = createNativeInstall() + coEvery { + applicationRepository.updateFusedDownloadWithDownloadingInfo(Source.PLAY_STORE, appInstall) + } throws CancellationException("cancelled") + + assertFailsWith { + refresher.updateDownloadUrls(appInstall, false) + } + } + + private fun createNativeInstall(isFree: Boolean = true) = AppInstall( + type = Type.NATIVE, + source = Source.PLAY_STORE, + id = "123", + status = Status.AWAITING, + packageName = "com.example.app", + isFree = isFree + ) +} -- GitLab From 45e1cfce3636ef1e25c2edb03bb7d616e5d0d24b Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Wed, 18 Mar 2026 01:35:21 +0600 Subject: [PATCH 14/43] test: cover install device preconditions Pin the network and storage guard rails from the install refactor so user-facing failure signals keep their current behavior. --- .../AppInstallDevicePreconditionsTest.kt | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 app/src/test/java/foundation/e/apps/installProcessor/AppInstallDevicePreconditionsTest.kt diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallDevicePreconditionsTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallDevicePreconditionsTest.kt new file mode 100644 index 000000000..b67ae8ee2 --- /dev/null +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallDevicePreconditionsTest.kt @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.installProcessor + +import foundation.e.apps.R +import foundation.e.apps.data.event.AppEvent +import foundation.e.apps.data.install.AppManagerWrapper +import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.install.notification.StorageNotificationManager +import foundation.e.apps.data.install.workmanager.AppInstallDevicePreconditions +import foundation.e.apps.data.install.wrapper.NetworkStatusChecker +import foundation.e.apps.data.install.wrapper.StorageSpaceChecker +import foundation.e.apps.domain.model.install.Status +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class AppInstallDevicePreconditionsTest { + private lateinit var appManagerWrapper: AppManagerWrapper + private lateinit var appEventDispatcher: FakeAppEventDispatcher + private lateinit var storageNotificationManager: StorageNotificationManager + private lateinit var storageSpaceChecker: StorageSpaceChecker + private lateinit var networkStatusChecker: NetworkStatusChecker + private lateinit var preconditions: AppInstallDevicePreconditions + + @Before + fun setup() { + appManagerWrapper = mockk(relaxed = true) + appEventDispatcher = FakeAppEventDispatcher() + storageNotificationManager = mockk(relaxed = true) + storageSpaceChecker = mockk(relaxed = true) + networkStatusChecker = mockk(relaxed = true) + preconditions = AppInstallDevicePreconditions( + appManagerWrapper, + appEventDispatcher, + storageNotificationManager, + storageSpaceChecker, + networkStatusChecker + ) + } + + @Test + fun canProceed_returnsFalseWhenNetworkUnavailable() = runTest { + val appInstall = createInstall() + every { networkStatusChecker.isNetworkAvailable() } returns false + + val result = preconditions.canProceed(appInstall) + + assertFalse(result) + coVerify { appManagerWrapper.installationIssue(appInstall) } + verify(exactly = 0) { storageSpaceChecker.spaceMissing(any()) } + assertTrue(appEventDispatcher.events.any { + it is AppEvent.NoInternetEvent && it.data == false + }) + } + + @Test + fun canProceed_returnsFalseWhenStorageIsMissing() = runTest { + val appInstall = createInstall() + every { networkStatusChecker.isNetworkAvailable() } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 512L + + val result = preconditions.canProceed(appInstall) + + assertFalse(result) + verify { storageNotificationManager.showNotEnoughSpaceNotification(appInstall) } + coVerify { appManagerWrapper.installationIssue(appInstall) } + assertTrue(appEventDispatcher.events.any { + it is AppEvent.ErrorMessageEvent && it.data == R.string.not_enough_storage + }) + } + + @Test + fun canProceed_returnsTrueWhenNetworkAndStorageChecksPass() = runTest { + val appInstall = createInstall() + every { networkStatusChecker.isNetworkAvailable() } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + + val result = preconditions.canProceed(appInstall) + + assertTrue(result) + coVerify(exactly = 0) { appManagerWrapper.installationIssue(any()) } + verify(exactly = 0) { storageNotificationManager.showNotEnoughSpaceNotification(any()) } + assertTrue(appEventDispatcher.events.isEmpty()) + } + + private fun createInstall() = AppInstall( + id = "123", + status = Status.AWAITING, + name = "Example App", + packageName = "com.example.app", + appSize = 1024L + ) +} -- GitLab From 236f15211e2a2843234f4577f5fa3f55230d6fe4 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Wed, 18 Mar 2026 01:38:00 +0600 Subject: [PATCH 15/43] test: cover install worker result handling Lock down the worker boundary from the install refactor so input validation and processor result mapping stay stable. --- .../installProcessor/InstallAppWorkerTest.kt | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 app/src/test/java/foundation/e/apps/installProcessor/InstallAppWorkerTest.kt diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallAppWorkerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallAppWorkerTest.kt new file mode 100644 index 000000000..327f18b6a --- /dev/null +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallAppWorkerTest.kt @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.installProcessor + +import android.os.Build +import androidx.test.core.app.ApplicationProvider +import androidx.work.Data +import com.google.common.truth.Truth.assertThat +import foundation.e.apps.data.enums.ResultStatus +import foundation.e.apps.data.install.workmanager.AppInstallProcessor +import foundation.e.apps.data.install.workmanager.InstallAppWorker +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [Build.VERSION_CODES.R]) +@OptIn(ExperimentalCoroutinesApi::class) +class InstallAppWorkerTest { + private lateinit var appInstallProcessor: AppInstallProcessor + + @Before + fun setup() { + appInstallProcessor = mockk(relaxed = true) + } + + @Test + fun doWork_returnsFailureWhenFusedDownloadIdIsMissing() = runTest { + val worker = createWorker(Data.EMPTY) + + val result = worker.doWork() + + assertThat(result).isEqualTo(androidx.work.ListenableWorker.Result.failure()) + coVerify(exactly = 0) { appInstallProcessor.processInstall(any(), any(), any()) } + } + + @Test + fun doWork_returnsSuccessWhenProcessorSucceeds() = runTest { + coEvery { + appInstallProcessor.processInstall("123", true, any()) + } returns Result.success(ResultStatus.OK) + val worker = createWorker( + Data.Builder() + .putString(InstallAppWorker.INPUT_DATA_FUSED_DOWNLOAD, "123") + .putBoolean(InstallAppWorker.IS_UPDATE_WORK, true) + .build() + ) + + val result = worker.doWork() + + assertThat(result).isEqualTo(androidx.work.ListenableWorker.Result.success()) + coVerify { appInstallProcessor.processInstall("123", true, any()) } + } + + @Test + fun doWork_returnsFailureWhenProcessorFails() = runTest { + coEvery { + appInstallProcessor.processInstall("123", false, any()) + } returns Result.failure(IllegalStateException("boom")) + val worker = createWorker( + Data.Builder() + .putString(InstallAppWorker.INPUT_DATA_FUSED_DOWNLOAD, "123") + .putBoolean(InstallAppWorker.IS_UPDATE_WORK, false) + .build() + ) + + val result = worker.doWork() + + assertThat(result).isEqualTo(androidx.work.ListenableWorker.Result.failure()) + coVerify { appInstallProcessor.processInstall("123", false, any()) } + } + + private fun createWorker(inputData: Data): InstallAppWorker { + val params = mockk(relaxed = true) + every { params.inputData } returns inputData + + return InstallAppWorker( + ApplicationProvider.getApplicationContext(), + params, + appInstallProcessor + ) + } +} -- GitLab From 5a40d0111cd786fd5f55e2ee5be68f01802ac1f5 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Wed, 18 Mar 2026 20:35:22 +0600 Subject: [PATCH 16/43] refactor: move AppInstallProcessor to install/core package --- .../{workmanager => core}/AppInstallProcessor.kt | 13 ++++++++----- .../e/apps/data/install/updates/UpdatesWorker.kt | 2 +- .../data/install/workmanager/InstallAppWorker.kt | 1 + .../e/apps/domain/install/InstallAppByIdUseCase.kt | 2 +- .../foundation/e/apps/ui/MainActivityViewModel.kt | 2 +- .../apps/data/install/updates/UpdatesWorkerTest.kt | 2 +- .../installProcessor/AppInstallProcessorTest.kt | 2 +- .../e/apps/installProcessor/InstallAppWorkerTest.kt | 2 +- 8 files changed, 15 insertions(+), 11 deletions(-) rename app/src/main/java/foundation/e/apps/data/install/{workmanager => core}/AppInstallProcessor.kt (81%) 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/core/AppInstallProcessor.kt similarity index 81% rename from app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallProcessor.kt rename to app/src/main/java/foundation/e/apps/data/install/core/AppInstallProcessor.kt index 1301363d9..63a412c2d 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallProcessor.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/AppInstallProcessor.kt @@ -1,6 +1,5 @@ /* - * Copyright MURENA SAS 2023 - * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2026 e Foundation * * 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 @@ -14,14 +13,18 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . + * */ -package foundation.e.apps.data.install.workmanager +package foundation.e.apps.data.install.core import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.install.AppInstallComponents import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.install.workmanager.AppInstallRequestFactory +import foundation.e.apps.data.install.workmanager.AppInstallStartCoordinator +import foundation.e.apps.data.install.workmanager.AppInstallWorkRunner import foundation.e.apps.domain.model.install.Status import javax.inject.Inject @@ -32,7 +35,7 @@ class AppInstallProcessor @Inject constructor( private val appInstallRequestFactory: AppInstallRequestFactory, ) { /** - * creates [AppInstall] from [Application] and enqueues into WorkManager to run install process. + * creates [foundation.e.apps.data.install.models.AppInstall] from [foundation.e.apps.data.application.data.Application] and enqueues into WorkManager to run install process. * @param application represents the app info which will be installed * @param isAnUpdate indicates the app is requested for update or not * @@ -51,7 +54,7 @@ class AppInstallProcessor @Inject constructor( } /** - * Enqueues [AppInstall] into WorkManager to run app install process. Before enqueuing, + * Enqueues [foundation.e.apps.data.install.models.AppInstall] into WorkManager to run app install process. Before enqueuing, * It validates some corner cases * @param appInstall represents the app downloading and installing related info, example- Installing Status, * Url of the APK,OBB files are needed to be downloaded and installed etc. diff --git a/app/src/main/java/foundation/e/apps/data/install/updates/UpdatesWorker.kt b/app/src/main/java/foundation/e/apps/data/install/updates/UpdatesWorker.kt index c8f7a48a9..5af1859a2 100644 --- a/app/src/main/java/foundation/e/apps/data/install/updates/UpdatesWorker.kt +++ b/app/src/main/java/foundation/e/apps/data/install/updates/UpdatesWorker.kt @@ -20,7 +20,7 @@ import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.event.EventBus import foundation.e.apps.data.gitlab.SystemAppsUpdatesRepository -import foundation.e.apps.data.install.workmanager.AppInstallProcessor +import foundation.e.apps.data.install.core.AppInstallProcessor import foundation.e.apps.data.updates.UpdatesManagerRepository import foundation.e.apps.domain.model.User import foundation.e.apps.domain.preferences.AppPreferencesRepository 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 6ea50e733..97c787556 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 @@ -32,6 +32,7 @@ import androidx.work.WorkerParameters import dagger.assisted.Assisted import dagger.assisted.AssistedInject import foundation.e.apps.R +import foundation.e.apps.data.install.core.AppInstallProcessor import java.util.concurrent.atomic.AtomicInteger @HiltWorker diff --git a/app/src/main/java/foundation/e/apps/domain/install/InstallAppByIdUseCase.kt b/app/src/main/java/foundation/e/apps/domain/install/InstallAppByIdUseCase.kt index f4dd5983e..43cfa0385 100644 --- a/app/src/main/java/foundation/e/apps/domain/install/InstallAppByIdUseCase.kt +++ b/app/src/main/java/foundation/e/apps/domain/install/InstallAppByIdUseCase.kt @@ -21,9 +21,9 @@ package foundation.e.apps.domain.install import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.Source import foundation.e.apps.data.install.AppInstallRepository +import foundation.e.apps.data.install.core.AppInstallProcessor import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PwaManager -import foundation.e.apps.data.install.workmanager.AppInstallProcessor import foundation.e.apps.domain.model.install.Status import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow diff --git a/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt b/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt index b7e2cd50d..3298a7b05 100644 --- a/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt @@ -36,7 +36,7 @@ import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PwaManager -import foundation.e.apps.data.install.workmanager.AppInstallProcessor +import foundation.e.apps.data.install.core.AppInstallProcessor import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.system.NetworkStatusManager import foundation.e.apps.domain.application.ApplicationDomain diff --git a/app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkerTest.kt b/app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkerTest.kt index 59ccab3f2..f3e07e015 100644 --- a/app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkerTest.kt +++ b/app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkerTest.kt @@ -43,7 +43,7 @@ import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.enums.Source import foundation.e.apps.domain.model.install.Status import foundation.e.apps.data.gitlab.SystemAppsUpdatesRepository -import foundation.e.apps.data.install.workmanager.AppInstallProcessor +import foundation.e.apps.data.install.core.AppInstallProcessor import foundation.e.apps.data.updates.UpdatesManagerRepository import foundation.e.apps.domain.model.LoginState import foundation.e.apps.domain.model.User diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt index 6755c9f32..11908eca1 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt @@ -28,7 +28,7 @@ import foundation.e.apps.data.install.AppInstallComponents import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.install.workmanager.AppInstallProcessor +import foundation.e.apps.data.install.core.AppInstallProcessor import foundation.e.apps.data.install.workmanager.AppInstallRequestFactory import foundation.e.apps.data.install.workmanager.AppInstallStartCoordinator import foundation.e.apps.data.install.workmanager.AppInstallWorkRunner diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallAppWorkerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallAppWorkerTest.kt index 327f18b6a..8efe9bf3d 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/InstallAppWorkerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallAppWorkerTest.kt @@ -23,7 +23,7 @@ import androidx.test.core.app.ApplicationProvider import androidx.work.Data import com.google.common.truth.Truth.assertThat import foundation.e.apps.data.enums.ResultStatus -import foundation.e.apps.data.install.workmanager.AppInstallProcessor +import foundation.e.apps.data.install.core.AppInstallProcessor import foundation.e.apps.data.install.workmanager.InstallAppWorker import io.mockk.coEvery import io.mockk.coVerify -- GitLab From 2c0db4e2efe14caeb91a586e142302873e1106f4 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Wed, 18 Mar 2026 20:39:15 +0600 Subject: [PATCH 17/43] refactor: move AppInstallProcessor's collaborator classes into install/core/helper package --- .../e/apps/data/install/core/AppInstallProcessor.kt | 6 +++--- .../helper}/AppInstallAgeLimitGate.kt | 2 +- .../helper}/AppInstallDevicePreconditions.kt | 2 +- .../helper}/AppInstallDownloadUrlRefresher.kt | 2 +- .../helper}/AppInstallPreEnqueueChecker.kt | 2 +- .../helper}/AppInstallRequestFactory.kt | 2 +- .../helper}/AppInstallStartCoordinator.kt | 3 ++- .../helper}/AppInstallWorkRunner.kt | 2 +- .../helper}/AppUpdateCompletionHandler.kt | 2 +- .../installProcessor/AppInstallAgeLimitGateTest.kt | 2 +- .../AppInstallDevicePreconditionsTest.kt | 2 +- .../AppInstallDownloadUrlRefresherTest.kt | 2 +- .../AppInstallPreEnqueueCheckerTest.kt | 8 ++++---- .../e/apps/installProcessor/AppInstallProcessorTest.kt | 6 +++--- .../installProcessor/AppInstallRequestFactoryTest.kt | 2 +- .../installProcessor/AppInstallStartCoordinatorTest.kt | 10 +++++----- .../apps/installProcessor/AppInstallWorkRunnerTest.kt | 4 ++-- .../installProcessor/AppUpdateCompletionHandlerTest.kt | 2 +- 18 files changed, 31 insertions(+), 30 deletions(-) rename app/src/main/java/foundation/e/apps/data/install/{workmanager => core/helper}/AppInstallAgeLimitGate.kt (98%) rename app/src/main/java/foundation/e/apps/data/install/{workmanager => core/helper}/AppInstallDevicePreconditions.kt (98%) rename app/src/main/java/foundation/e/apps/data/install/{workmanager => core/helper}/AppInstallDownloadUrlRefresher.kt (98%) rename app/src/main/java/foundation/e/apps/data/install/{workmanager => core/helper}/AppInstallPreEnqueueChecker.kt (97%) rename app/src/main/java/foundation/e/apps/data/install/{workmanager => core/helper}/AppInstallRequestFactory.kt (97%) rename app/src/main/java/foundation/e/apps/data/install/{workmanager => core/helper}/AppInstallStartCoordinator.kt (97%) rename app/src/main/java/foundation/e/apps/data/install/{workmanager => core/helper}/AppInstallWorkRunner.kt (99%) rename app/src/main/java/foundation/e/apps/data/install/{workmanager => core/helper}/AppUpdateCompletionHandler.kt (98%) diff --git a/app/src/main/java/foundation/e/apps/data/install/core/AppInstallProcessor.kt b/app/src/main/java/foundation/e/apps/data/install/core/AppInstallProcessor.kt index 63a412c2d..16bbbad04 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/AppInstallProcessor.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/AppInstallProcessor.kt @@ -22,9 +22,9 @@ import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.install.AppInstallComponents import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.install.workmanager.AppInstallRequestFactory -import foundation.e.apps.data.install.workmanager.AppInstallStartCoordinator -import foundation.e.apps.data.install.workmanager.AppInstallWorkRunner +import foundation.e.apps.data.install.core.helper.AppInstallRequestFactory +import foundation.e.apps.data.install.core.helper.AppInstallStartCoordinator +import foundation.e.apps.data.install.core.helper.AppInstallWorkRunner import foundation.e.apps.domain.model.install.Status import javax.inject.Inject diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallAgeLimitGate.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallAgeLimitGate.kt similarity index 98% rename from app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallAgeLimitGate.kt rename to app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallAgeLimitGate.kt index c98d5e1e1..4c4520d82 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallAgeLimitGate.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallAgeLimitGate.kt @@ -16,7 +16,7 @@ * */ -package foundation.e.apps.data.install.workmanager +package foundation.e.apps.data.install.core.helper import foundation.e.apps.R import foundation.e.apps.data.ResultSupreme diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallDevicePreconditions.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallDevicePreconditions.kt similarity index 98% rename from app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallDevicePreconditions.kt rename to app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallDevicePreconditions.kt index e43499ad4..538ea9ce4 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallDevicePreconditions.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallDevicePreconditions.kt @@ -16,7 +16,7 @@ * */ -package foundation.e.apps.data.install.workmanager +package foundation.e.apps.data.install.core.helper import foundation.e.apps.R import foundation.e.apps.data.event.AppEvent diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallDownloadUrlRefresher.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallDownloadUrlRefresher.kt similarity index 98% rename from app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallDownloadUrlRefresher.kt rename to app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallDownloadUrlRefresher.kt index bc4ac8c6a..dcf84a42b 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallDownloadUrlRefresher.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallDownloadUrlRefresher.kt @@ -16,7 +16,7 @@ * */ -package foundation.e.apps.data.install.workmanager +package foundation.e.apps.data.install.core.helper import com.aurora.gplayapi.exceptions.InternalException import foundation.e.apps.data.ResultSupreme diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallPreEnqueueChecker.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallPreEnqueueChecker.kt similarity index 97% rename from app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallPreEnqueueChecker.kt rename to app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallPreEnqueueChecker.kt index 04684a195..d307cc1d6 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallPreEnqueueChecker.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallPreEnqueueChecker.kt @@ -16,7 +16,7 @@ * */ -package foundation.e.apps.data.install.workmanager +package foundation.e.apps.data.install.core.helper import foundation.e.apps.data.enums.Type import foundation.e.apps.data.install.AppManagerWrapper diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallRequestFactory.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallRequestFactory.kt similarity index 97% rename from app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallRequestFactory.kt rename to app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallRequestFactory.kt index 2045c39bf..17aabba00 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallRequestFactory.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallRequestFactory.kt @@ -16,7 +16,7 @@ * */ -package foundation.e.apps.data.install.workmanager +package foundation.e.apps.data.install.core.helper import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.Source diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallStartCoordinator.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallStartCoordinator.kt similarity index 97% rename from app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallStartCoordinator.kt rename to app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallStartCoordinator.kt index c0e11f43e..afb262d49 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallStartCoordinator.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallStartCoordinator.kt @@ -16,7 +16,7 @@ * */ -package foundation.e.apps.data.install.workmanager +package foundation.e.apps.data.install.core.helper import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext @@ -24,6 +24,7 @@ import foundation.e.apps.R import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.install.workmanager.InstallWorkManager import foundation.e.apps.data.install.wrapper.AppEventDispatcher import foundation.e.apps.data.preference.PlayStoreAuthStore import foundation.e.apps.domain.model.User diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallWorkRunner.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallWorkRunner.kt similarity index 99% rename from app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallWorkRunner.kt rename to app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallWorkRunner.kt index ab59f1e12..3cb430e6f 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallWorkRunner.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallWorkRunner.kt @@ -16,7 +16,7 @@ * */ -package foundation.e.apps.data.install.workmanager +package foundation.e.apps.data.install.core.helper import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.install.AppInstallRepository diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppUpdateCompletionHandler.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppUpdateCompletionHandler.kt similarity index 98% rename from app/src/main/java/foundation/e/apps/data/install/workmanager/AppUpdateCompletionHandler.kt rename to app/src/main/java/foundation/e/apps/data/install/core/helper/AppUpdateCompletionHandler.kt index b68636dc6..88a8ba843 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppUpdateCompletionHandler.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppUpdateCompletionHandler.kt @@ -16,7 +16,7 @@ * */ -package foundation.e.apps.data.install.workmanager +package foundation.e.apps.data.install.core.helper import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallAgeLimitGateTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallAgeLimitGateTest.kt index 19de4f0f8..675ad042d 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallAgeLimitGateTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallAgeLimitGateTest.kt @@ -24,7 +24,7 @@ import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.install.workmanager.AppInstallAgeLimitGate +import foundation.e.apps.data.install.core.helper.AppInstallAgeLimitGate import foundation.e.apps.data.install.wrapper.ParentalControlAuthGateway import foundation.e.apps.domain.ValidateAppAgeLimitUseCase import foundation.e.apps.domain.model.ContentRatingValidity diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallDevicePreconditionsTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallDevicePreconditionsTest.kt index b67ae8ee2..627c9e3e5 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallDevicePreconditionsTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallDevicePreconditionsTest.kt @@ -23,7 +23,7 @@ import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager -import foundation.e.apps.data.install.workmanager.AppInstallDevicePreconditions +import foundation.e.apps.data.install.core.helper.AppInstallDevicePreconditions import foundation.e.apps.data.install.wrapper.NetworkStatusChecker import foundation.e.apps.data.install.wrapper.StorageSpaceChecker import foundation.e.apps.domain.model.install.Status diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallDownloadUrlRefresherTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallDownloadUrlRefresherTest.kt index 6be024abf..2d5afeac6 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallDownloadUrlRefresherTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallDownloadUrlRefresherTest.kt @@ -27,7 +27,7 @@ import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.install.workmanager.AppInstallDownloadUrlRefresher +import foundation.e.apps.data.install.core.helper.AppInstallDownloadUrlRefresher import foundation.e.apps.data.playstore.utils.GplayHttpRequestException import foundation.e.apps.domain.model.install.Status import io.mockk.coEvery diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt index 085af08db..58e7efdd4 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt @@ -22,10 +22,10 @@ import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Type import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.install.workmanager.AppInstallAgeLimitGate -import foundation.e.apps.data.install.workmanager.AppInstallDevicePreconditions -import foundation.e.apps.data.install.workmanager.AppInstallDownloadUrlRefresher -import foundation.e.apps.data.install.workmanager.AppInstallPreEnqueueChecker +import foundation.e.apps.data.install.core.helper.AppInstallAgeLimitGate +import foundation.e.apps.data.install.core.helper.AppInstallDevicePreconditions +import foundation.e.apps.data.install.core.helper.AppInstallDownloadUrlRefresher +import foundation.e.apps.data.install.core.helper.AppInstallPreEnqueueChecker import foundation.e.apps.domain.model.install.Status import io.mockk.coEvery import io.mockk.coVerify diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt index 11908eca1..f25697c84 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt @@ -29,9 +29,9 @@ import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.core.AppInstallProcessor -import foundation.e.apps.data.install.workmanager.AppInstallRequestFactory -import foundation.e.apps.data.install.workmanager.AppInstallStartCoordinator -import foundation.e.apps.data.install.workmanager.AppInstallWorkRunner +import foundation.e.apps.data.install.core.helper.AppInstallRequestFactory +import foundation.e.apps.data.install.core.helper.AppInstallStartCoordinator +import foundation.e.apps.data.install.core.helper.AppInstallWorkRunner import foundation.e.apps.util.MainCoroutineRule import io.mockk.coEvery import io.mockk.coVerify diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallRequestFactoryTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallRequestFactoryTest.kt index 53c5a28ef..068e49d64 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallRequestFactoryTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallRequestFactoryTest.kt @@ -22,7 +22,7 @@ import com.aurora.gplayapi.data.models.ContentRating import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Type -import foundation.e.apps.data.install.workmanager.AppInstallRequestFactory +import foundation.e.apps.data.install.core.helper.AppInstallRequestFactory import foundation.e.apps.domain.model.install.Status import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt index 9975a676a..288977a11 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt @@ -31,11 +31,11 @@ import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager -import foundation.e.apps.data.install.workmanager.AppInstallAgeLimitGate -import foundation.e.apps.data.install.workmanager.AppInstallDevicePreconditions -import foundation.e.apps.data.install.workmanager.AppInstallDownloadUrlRefresher -import foundation.e.apps.data.install.workmanager.AppInstallPreEnqueueChecker -import foundation.e.apps.data.install.workmanager.AppInstallStartCoordinator +import foundation.e.apps.data.install.core.helper.AppInstallAgeLimitGate +import foundation.e.apps.data.install.core.helper.AppInstallDevicePreconditions +import foundation.e.apps.data.install.core.helper.AppInstallDownloadUrlRefresher +import foundation.e.apps.data.install.core.helper.AppInstallPreEnqueueChecker +import foundation.e.apps.data.install.core.helper.AppInstallStartCoordinator import foundation.e.apps.data.install.workmanager.InstallWorkManager import foundation.e.apps.data.install.wrapper.NetworkStatusChecker import foundation.e.apps.data.install.wrapper.StorageSpaceChecker diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallWorkRunnerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallWorkRunnerTest.kt index 3e0e3da90..fce9d6dc1 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallWorkRunnerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallWorkRunnerTest.kt @@ -25,8 +25,8 @@ import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.download.DownloadManagerUtils import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.install.workmanager.AppInstallWorkRunner -import foundation.e.apps.data.install.workmanager.AppUpdateCompletionHandler +import foundation.e.apps.data.install.core.helper.AppInstallWorkRunner +import foundation.e.apps.data.install.core.helper.AppUpdateCompletionHandler import foundation.e.apps.domain.model.install.Status import foundation.e.apps.util.MainCoroutineRule import io.mockk.mockk diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppUpdateCompletionHandlerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppUpdateCompletionHandlerTest.kt index c27314a7f..6b8de8e47 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppUpdateCompletionHandlerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppUpdateCompletionHandlerTest.kt @@ -24,7 +24,7 @@ import foundation.e.apps.R import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.install.workmanager.AppUpdateCompletionHandler +import foundation.e.apps.data.install.core.helper.AppUpdateCompletionHandler import foundation.e.apps.data.install.wrapper.UpdatesNotificationSender import foundation.e.apps.data.install.wrapper.UpdatesTracker import foundation.e.apps.data.preference.PlayStoreAuthStore -- GitLab From 453deb63105410940dad63cffa918495df2fa478 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Wed, 18 Mar 2026 20:41:21 +0600 Subject: [PATCH 18/43] refactor: rename AppInstallAgeLimitGate to AgeLimitGate --- ...pInstallAgeLimitGate.kt => AgeLimitGate.kt} | 2 +- .../core/helper/AppInstallPreEnqueueChecker.kt | 4 ++-- ...AgeLimitGateTest.kt => AgeLimitGateTest.kt} | 8 ++++---- .../AppInstallPreEnqueueCheckerTest.kt | 18 +++++++++--------- .../AppInstallStartCoordinatorTest.kt | 18 +++++++++--------- 5 files changed, 25 insertions(+), 25 deletions(-) rename app/src/main/java/foundation/e/apps/data/install/core/helper/{AppInstallAgeLimitGate.kt => AgeLimitGate.kt} (98%) rename app/src/test/java/foundation/e/apps/installProcessor/{AppInstallAgeLimitGateTest.kt => AgeLimitGateTest.kt} (96%) diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallAgeLimitGate.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/AgeLimitGate.kt similarity index 98% rename from app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallAgeLimitGate.kt rename to app/src/main/java/foundation/e/apps/data/install/core/helper/AgeLimitGate.kt index 4c4520d82..2023908a8 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallAgeLimitGate.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/AgeLimitGate.kt @@ -30,7 +30,7 @@ import foundation.e.apps.domain.model.ContentRatingValidity import kotlinx.coroutines.CompletableDeferred import javax.inject.Inject -class AppInstallAgeLimitGate @Inject constructor( +class AgeLimitGate @Inject constructor( private val validateAppAgeLimitUseCase: ValidateAppAgeLimitUseCase, private val appManagerWrapper: AppManagerWrapper, private val appEventDispatcher: AppEventDispatcher, diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallPreEnqueueChecker.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallPreEnqueueChecker.kt index d307cc1d6..a2d40b2cf 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallPreEnqueueChecker.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallPreEnqueueChecker.kt @@ -27,7 +27,7 @@ import javax.inject.Inject class AppInstallPreEnqueueChecker @Inject constructor( private val appInstallDownloadUrlRefresher: AppInstallDownloadUrlRefresher, private val appManagerWrapper: AppManagerWrapper, - private val appInstallAgeLimitGate: AppInstallAgeLimitGate, + private val ageLimitGate: AgeLimitGate, private val appInstallDevicePreconditions: AppInstallDevicePreconditions, ) { suspend fun canEnqueue(appInstall: AppInstall, isAnUpdate: Boolean = false): Boolean { @@ -35,7 +35,7 @@ class AppInstallPreEnqueueChecker @Inject constructor( appInstallDownloadUrlRefresher.updateDownloadUrls(appInstall, isAnUpdate) val isDownloadAdded = hasUpdatedDownloadUrls && addDownload(appInstall) - val isAgeLimitAllowed = isDownloadAdded && appInstallAgeLimitGate.allow(appInstall) + val isAgeLimitAllowed = isDownloadAdded && ageLimitGate.allow(appInstall) return isAgeLimitAllowed && appInstallDevicePreconditions.canProceed(appInstall) } diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallAgeLimitGateTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AgeLimitGateTest.kt similarity index 96% rename from app/src/test/java/foundation/e/apps/installProcessor/AppInstallAgeLimitGateTest.kt rename to app/src/test/java/foundation/e/apps/installProcessor/AgeLimitGateTest.kt index 675ad042d..8eccf4606 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallAgeLimitGateTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AgeLimitGateTest.kt @@ -24,7 +24,7 @@ import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.install.core.helper.AppInstallAgeLimitGate +import foundation.e.apps.data.install.core.helper.AgeLimitGate import foundation.e.apps.data.install.wrapper.ParentalControlAuthGateway import foundation.e.apps.domain.ValidateAppAgeLimitUseCase import foundation.e.apps.domain.model.ContentRatingValidity @@ -41,12 +41,12 @@ import org.junit.Test import org.mockito.Mockito @OptIn(ExperimentalCoroutinesApi::class) -class AppInstallAgeLimitGateTest { +class AgeLimitGateTest { private lateinit var validateAppAgeLimitUseCase: ValidateAppAgeLimitUseCase private lateinit var appManagerWrapper: AppManagerWrapper private lateinit var parentalControlAuthGateway: ParentalControlAuthGateway private lateinit var appEventDispatcher: FakeAppEventDispatcher - private lateinit var gate: AppInstallAgeLimitGate + private lateinit var gate: AgeLimitGate @Before fun setup() { @@ -54,7 +54,7 @@ class AppInstallAgeLimitGateTest { appManagerWrapper = mockk(relaxed = true) parentalControlAuthGateway = mockk(relaxed = true) appEventDispatcher = FakeAppEventDispatcher(autoCompleteDeferred = true) - gate = AppInstallAgeLimitGate( + gate = AgeLimitGate( validateAppAgeLimitUseCase, appManagerWrapper, appEventDispatcher, diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt index 58e7efdd4..38f3bf5dc 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt @@ -22,7 +22,7 @@ import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Type import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.install.core.helper.AppInstallAgeLimitGate +import foundation.e.apps.data.install.core.helper.AgeLimitGate import foundation.e.apps.data.install.core.helper.AppInstallDevicePreconditions import foundation.e.apps.data.install.core.helper.AppInstallDownloadUrlRefresher import foundation.e.apps.data.install.core.helper.AppInstallPreEnqueueChecker @@ -41,7 +41,7 @@ import org.junit.Test class AppInstallPreEnqueueCheckerTest { private lateinit var appInstallDownloadUrlRefresher: AppInstallDownloadUrlRefresher private lateinit var appManagerWrapper: AppManagerWrapper - private lateinit var appInstallAgeLimitGate: AppInstallAgeLimitGate + private lateinit var ageLimitGate: AgeLimitGate private lateinit var appInstallDevicePreconditions: AppInstallDevicePreconditions private lateinit var checker: AppInstallPreEnqueueChecker @@ -49,12 +49,12 @@ class AppInstallPreEnqueueCheckerTest { fun setup() { appInstallDownloadUrlRefresher = mockk(relaxed = true) appManagerWrapper = mockk(relaxed = true) - appInstallAgeLimitGate = mockk(relaxed = true) + ageLimitGate = mockk(relaxed = true) appInstallDevicePreconditions = mockk(relaxed = true) checker = AppInstallPreEnqueueChecker( appInstallDownloadUrlRefresher, appManagerWrapper, - appInstallAgeLimitGate, + ageLimitGate, appInstallDevicePreconditions ) } @@ -63,7 +63,7 @@ class AppInstallPreEnqueueCheckerTest { fun canEnqueue_skipsDownloadUrlRefreshForPwaInstalls() = runTest { val appInstall = createPwaInstall() coEvery { appManagerWrapper.addDownload(appInstall) } returns true - coEvery { appInstallAgeLimitGate.allow(appInstall) } returns true + coEvery { ageLimitGate.allow(appInstall) } returns true coEvery { appInstallDevicePreconditions.canProceed(appInstall) } returns true val result = checker.canEnqueue(appInstall) @@ -83,7 +83,7 @@ class AppInstallPreEnqueueCheckerTest { assertFalse(result) coVerify(exactly = 0) { appManagerWrapper.addDownload(any()) } - coVerify(exactly = 0) { appInstallAgeLimitGate.allow(any()) } + coVerify(exactly = 0) { ageLimitGate.allow(any()) } coVerify(exactly = 0) { appInstallDevicePreconditions.canProceed(any()) } } @@ -96,7 +96,7 @@ class AppInstallPreEnqueueCheckerTest { val result = checker.canEnqueue(appInstall) assertFalse(result) - coVerify(exactly = 0) { appInstallAgeLimitGate.allow(any()) } + coVerify(exactly = 0) { ageLimitGate.allow(any()) } coVerify(exactly = 0) { appInstallDevicePreconditions.canProceed(any()) } } @@ -105,7 +105,7 @@ class AppInstallPreEnqueueCheckerTest { val appInstall = createNativeInstall() coEvery { appInstallDownloadUrlRefresher.updateDownloadUrls(appInstall, false) } returns true coEvery { appManagerWrapper.addDownload(appInstall) } returns true - coEvery { appInstallAgeLimitGate.allow(appInstall) } returns false + coEvery { ageLimitGate.allow(appInstall) } returns false val result = checker.canEnqueue(appInstall) @@ -118,7 +118,7 @@ class AppInstallPreEnqueueCheckerTest { val appInstall = createNativeInstall() coEvery { appInstallDownloadUrlRefresher.updateDownloadUrls(appInstall, false) } returns true coEvery { appManagerWrapper.addDownload(appInstall) } returns true - coEvery { appInstallAgeLimitGate.allow(appInstall) } returns true + coEvery { ageLimitGate.allow(appInstall) } returns true coEvery { appInstallDevicePreconditions.canProceed(appInstall) } returns true val result = checker.canEnqueue(appInstall) diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt index 288977a11..5ab790451 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt @@ -31,7 +31,7 @@ import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager -import foundation.e.apps.data.install.core.helper.AppInstallAgeLimitGate +import foundation.e.apps.data.install.core.helper.AgeLimitGate import foundation.e.apps.data.install.core.helper.AppInstallDevicePreconditions import foundation.e.apps.data.install.core.helper.AppInstallDownloadUrlRefresher import foundation.e.apps.data.install.core.helper.AppInstallPreEnqueueChecker @@ -68,7 +68,7 @@ class AppInstallStartCoordinatorTest { private lateinit var sessionRepository: SessionRepository private lateinit var playStoreAuthStore: PlayStoreAuthStore private lateinit var storageNotificationManager: StorageNotificationManager - private lateinit var appInstallAgeLimitGate: AppInstallAgeLimitGate + private lateinit var ageLimitGate: AgeLimitGate private lateinit var appEventDispatcher: FakeAppEventDispatcher private lateinit var storageSpaceChecker: StorageSpaceChecker private lateinit var networkStatusChecker: NetworkStatusChecker @@ -87,7 +87,7 @@ class AppInstallStartCoordinatorTest { sessionRepository = mockk(relaxed = true) playStoreAuthStore = mockk(relaxed = true) storageNotificationManager = mockk(relaxed = true) - appInstallAgeLimitGate = mockk(relaxed = true) + ageLimitGate = mockk(relaxed = true) appEventDispatcher = FakeAppEventDispatcher() storageSpaceChecker = mockk(relaxed = true) networkStatusChecker = mockk(relaxed = true) @@ -111,7 +111,7 @@ class AppInstallStartCoordinatorTest { preflightChecker = AppInstallPreEnqueueChecker( downloadUrlRefresher, appManagerWrapper, - appInstallAgeLimitGate, + ageLimitGate, devicePreconditions ) coordinator = AppInstallStartCoordinator( @@ -129,7 +129,7 @@ class AppInstallStartCoordinatorTest { val appInstall = createPwaInstall() coEvery { appManagerWrapper.addDownload(appInstall) } returns true - coEvery { appInstallAgeLimitGate.allow(appInstall) } returns true + coEvery { ageLimitGate.allow(appInstall) } returns true every { networkStatusChecker.isNetworkAvailable() } returns true every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L @@ -143,7 +143,7 @@ class AppInstallStartCoordinatorTest { val appInstall = createPwaInstall() coEvery { appManagerWrapper.addDownload(appInstall) } returns true - coEvery { appInstallAgeLimitGate.allow(appInstall) } returns true + coEvery { ageLimitGate.allow(appInstall) } returns true every { networkStatusChecker.isNetworkAvailable() } returns false every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L @@ -158,7 +158,7 @@ class AppInstallStartCoordinatorTest { val appInstall = createPwaInstall() coEvery { appManagerWrapper.addDownload(appInstall) } returns true - coEvery { appInstallAgeLimitGate.allow(appInstall) } returns true + coEvery { ageLimitGate.allow(appInstall) } returns true every { networkStatusChecker.isNetworkAvailable() } returns true every { storageSpaceChecker.spaceMissing(appInstall) } returns 100L @@ -178,7 +178,7 @@ class AppInstallStartCoordinatorTest { val result = coordinator.canEnqueue(appInstall) assertFalse(result) - coVerify(exactly = 0) { appInstallAgeLimitGate.allow(any()) } + coVerify(exactly = 0) { ageLimitGate.allow(any()) } } @Test @@ -192,7 +192,7 @@ class AppInstallStartCoordinatorTest { playStoreAuthStore.awaitAuthData() } returns AuthData(email = "anon@example.com", isAnonymous = true) coEvery { appManagerWrapper.addDownload(appInstall) } returns true - coEvery { appInstallAgeLimitGate.allow(appInstall) } returns true + coEvery { ageLimitGate.allow(appInstall) } returns true every { networkStatusChecker.isNetworkAvailable() } returns true every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L justRun { InstallWorkManager.enqueueWork(any(), any(), any()) } -- GitLab From fe871057814c33348fd89a887150e701e85efa76 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Wed, 18 Mar 2026 20:43:39 +0600 Subject: [PATCH 19/43] refactor: rename AppInstallDevicePreconditions to DevicePreconditions --- .../core/helper/AppInstallPreEnqueueChecker.kt | 4 ++-- ...Preconditions.kt => DevicePreconditions.kt} | 2 +- .../AppInstallPreEnqueueCheckerTest.kt | 18 +++++++++--------- .../AppInstallStartCoordinatorTest.kt | 6 +++--- ...tionsTest.kt => DevicePreconditionsTest.kt} | 8 ++++---- 5 files changed, 19 insertions(+), 19 deletions(-) rename app/src/main/java/foundation/e/apps/data/install/core/helper/{AppInstallDevicePreconditions.kt => DevicePreconditions.kt} (97%) rename app/src/test/java/foundation/e/apps/installProcessor/{AppInstallDevicePreconditionsTest.kt => DevicePreconditionsTest.kt} (94%) diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallPreEnqueueChecker.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallPreEnqueueChecker.kt index a2d40b2cf..6b3674689 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallPreEnqueueChecker.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallPreEnqueueChecker.kt @@ -28,7 +28,7 @@ class AppInstallPreEnqueueChecker @Inject constructor( private val appInstallDownloadUrlRefresher: AppInstallDownloadUrlRefresher, private val appManagerWrapper: AppManagerWrapper, private val ageLimitGate: AgeLimitGate, - private val appInstallDevicePreconditions: AppInstallDevicePreconditions, + private val devicePreconditions: DevicePreconditions, ) { suspend fun canEnqueue(appInstall: AppInstall, isAnUpdate: Boolean = false): Boolean { val hasUpdatedDownloadUrls = appInstall.type == Type.PWA || @@ -37,7 +37,7 @@ class AppInstallPreEnqueueChecker @Inject constructor( val isDownloadAdded = hasUpdatedDownloadUrls && addDownload(appInstall) val isAgeLimitAllowed = isDownloadAdded && ageLimitGate.allow(appInstall) - return isAgeLimitAllowed && appInstallDevicePreconditions.canProceed(appInstall) + return isAgeLimitAllowed && devicePreconditions.canProceed(appInstall) } private suspend fun addDownload(appInstall: AppInstall): Boolean { diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallDevicePreconditions.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/DevicePreconditions.kt similarity index 97% rename from app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallDevicePreconditions.kt rename to app/src/main/java/foundation/e/apps/data/install/core/helper/DevicePreconditions.kt index 538ea9ce4..3ba545b68 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallDevicePreconditions.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/DevicePreconditions.kt @@ -29,7 +29,7 @@ import foundation.e.apps.data.install.wrapper.StorageSpaceChecker import timber.log.Timber import javax.inject.Inject -class AppInstallDevicePreconditions @Inject constructor( +class DevicePreconditions @Inject constructor( private val appManagerWrapper: AppManagerWrapper, private val appEventDispatcher: AppEventDispatcher, private val storageNotificationManager: StorageNotificationManager, diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt index 38f3bf5dc..aa3993181 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt @@ -23,7 +23,7 @@ import foundation.e.apps.data.enums.Type import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.core.helper.AgeLimitGate -import foundation.e.apps.data.install.core.helper.AppInstallDevicePreconditions +import foundation.e.apps.data.install.core.helper.DevicePreconditions import foundation.e.apps.data.install.core.helper.AppInstallDownloadUrlRefresher import foundation.e.apps.data.install.core.helper.AppInstallPreEnqueueChecker import foundation.e.apps.domain.model.install.Status @@ -42,7 +42,7 @@ class AppInstallPreEnqueueCheckerTest { private lateinit var appInstallDownloadUrlRefresher: AppInstallDownloadUrlRefresher private lateinit var appManagerWrapper: AppManagerWrapper private lateinit var ageLimitGate: AgeLimitGate - private lateinit var appInstallDevicePreconditions: AppInstallDevicePreconditions + private lateinit var devicePreconditions: DevicePreconditions private lateinit var checker: AppInstallPreEnqueueChecker @Before @@ -50,12 +50,12 @@ class AppInstallPreEnqueueCheckerTest { appInstallDownloadUrlRefresher = mockk(relaxed = true) appManagerWrapper = mockk(relaxed = true) ageLimitGate = mockk(relaxed = true) - appInstallDevicePreconditions = mockk(relaxed = true) + devicePreconditions = mockk(relaxed = true) checker = AppInstallPreEnqueueChecker( appInstallDownloadUrlRefresher, appManagerWrapper, ageLimitGate, - appInstallDevicePreconditions + devicePreconditions ) } @@ -64,7 +64,7 @@ class AppInstallPreEnqueueCheckerTest { val appInstall = createPwaInstall() coEvery { appManagerWrapper.addDownload(appInstall) } returns true coEvery { ageLimitGate.allow(appInstall) } returns true - coEvery { appInstallDevicePreconditions.canProceed(appInstall) } returns true + coEvery { devicePreconditions.canProceed(appInstall) } returns true val result = checker.canEnqueue(appInstall) @@ -84,7 +84,7 @@ class AppInstallPreEnqueueCheckerTest { assertFalse(result) coVerify(exactly = 0) { appManagerWrapper.addDownload(any()) } coVerify(exactly = 0) { ageLimitGate.allow(any()) } - coVerify(exactly = 0) { appInstallDevicePreconditions.canProceed(any()) } + coVerify(exactly = 0) { devicePreconditions.canProceed(any()) } } @Test @@ -97,7 +97,7 @@ class AppInstallPreEnqueueCheckerTest { assertFalse(result) coVerify(exactly = 0) { ageLimitGate.allow(any()) } - coVerify(exactly = 0) { appInstallDevicePreconditions.canProceed(any()) } + coVerify(exactly = 0) { devicePreconditions.canProceed(any()) } } @Test @@ -110,7 +110,7 @@ class AppInstallPreEnqueueCheckerTest { val result = checker.canEnqueue(appInstall) assertFalse(result) - coVerify(exactly = 0) { appInstallDevicePreconditions.canProceed(any()) } + coVerify(exactly = 0) { devicePreconditions.canProceed(any()) } } @Test @@ -119,7 +119,7 @@ class AppInstallPreEnqueueCheckerTest { coEvery { appInstallDownloadUrlRefresher.updateDownloadUrls(appInstall, false) } returns true coEvery { appManagerWrapper.addDownload(appInstall) } returns true coEvery { ageLimitGate.allow(appInstall) } returns true - coEvery { appInstallDevicePreconditions.canProceed(appInstall) } returns true + coEvery { devicePreconditions.canProceed(appInstall) } returns true val result = checker.canEnqueue(appInstall) diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt index 5ab790451..92cee0acb 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt @@ -32,7 +32,7 @@ import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager import foundation.e.apps.data.install.core.helper.AgeLimitGate -import foundation.e.apps.data.install.core.helper.AppInstallDevicePreconditions +import foundation.e.apps.data.install.core.helper.DevicePreconditions import foundation.e.apps.data.install.core.helper.AppInstallDownloadUrlRefresher import foundation.e.apps.data.install.core.helper.AppInstallPreEnqueueChecker import foundation.e.apps.data.install.core.helper.AppInstallStartCoordinator @@ -73,7 +73,7 @@ class AppInstallStartCoordinatorTest { private lateinit var storageSpaceChecker: StorageSpaceChecker private lateinit var networkStatusChecker: NetworkStatusChecker private lateinit var appManager: AppManager - private lateinit var devicePreconditions: AppInstallDevicePreconditions + private lateinit var devicePreconditions: DevicePreconditions private lateinit var downloadUrlRefresher: AppInstallDownloadUrlRefresher private lateinit var preflightChecker: AppInstallPreEnqueueChecker private lateinit var coordinator: AppInstallStartCoordinator @@ -101,7 +101,7 @@ class AppInstallStartCoordinatorTest { appEventDispatcher, appManager ) - devicePreconditions = AppInstallDevicePreconditions( + devicePreconditions = DevicePreconditions( appManagerWrapper, appEventDispatcher, storageNotificationManager, diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallDevicePreconditionsTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/DevicePreconditionsTest.kt similarity index 94% rename from app/src/test/java/foundation/e/apps/installProcessor/AppInstallDevicePreconditionsTest.kt rename to app/src/test/java/foundation/e/apps/installProcessor/DevicePreconditionsTest.kt index 627c9e3e5..a4dc646e1 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallDevicePreconditionsTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/DevicePreconditionsTest.kt @@ -23,7 +23,7 @@ import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager -import foundation.e.apps.data.install.core.helper.AppInstallDevicePreconditions +import foundation.e.apps.data.install.core.helper.DevicePreconditions import foundation.e.apps.data.install.wrapper.NetworkStatusChecker import foundation.e.apps.data.install.wrapper.StorageSpaceChecker import foundation.e.apps.domain.model.install.Status @@ -39,13 +39,13 @@ import org.junit.Before import org.junit.Test @OptIn(ExperimentalCoroutinesApi::class) -class AppInstallDevicePreconditionsTest { +class DevicePreconditionsTest { private lateinit var appManagerWrapper: AppManagerWrapper private lateinit var appEventDispatcher: FakeAppEventDispatcher private lateinit var storageNotificationManager: StorageNotificationManager private lateinit var storageSpaceChecker: StorageSpaceChecker private lateinit var networkStatusChecker: NetworkStatusChecker - private lateinit var preconditions: AppInstallDevicePreconditions + private lateinit var preconditions: DevicePreconditions @Before fun setup() { @@ -54,7 +54,7 @@ class AppInstallDevicePreconditionsTest { storageNotificationManager = mockk(relaxed = true) storageSpaceChecker = mockk(relaxed = true) networkStatusChecker = mockk(relaxed = true) - preconditions = AppInstallDevicePreconditions( + preconditions = DevicePreconditions( appManagerWrapper, appEventDispatcher, storageNotificationManager, -- GitLab From dc17f57d68729218bb6d754e9c5d6589a82d918e Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Wed, 18 Mar 2026 20:45:04 +0600 Subject: [PATCH 20/43] refactor: rename AppInstallDownloadUrlRefresher to DownloadUrlRefresher --- .../core/helper/AppInstallPreEnqueueChecker.kt | 4 ++-- ...UrlRefresher.kt => DownloadUrlRefresher.kt} | 2 +- .../AppInstallPreEnqueueCheckerTest.kt | 18 +++++++++--------- .../AppInstallStartCoordinatorTest.kt | 6 +++--- ...sherTest.kt => DownloadUrlRefresherTest.kt} | 8 ++++---- 5 files changed, 19 insertions(+), 19 deletions(-) rename app/src/main/java/foundation/e/apps/data/install/core/helper/{AppInstallDownloadUrlRefresher.kt => DownloadUrlRefresher.kt} (98%) rename app/src/test/java/foundation/e/apps/installProcessor/{AppInstallDownloadUrlRefresherTest.kt => DownloadUrlRefresherTest.kt} (96%) diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallPreEnqueueChecker.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallPreEnqueueChecker.kt index 6b3674689..fd7efc0da 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallPreEnqueueChecker.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallPreEnqueueChecker.kt @@ -25,14 +25,14 @@ import timber.log.Timber import javax.inject.Inject class AppInstallPreEnqueueChecker @Inject constructor( - private val appInstallDownloadUrlRefresher: AppInstallDownloadUrlRefresher, + private val downloadUrlRefresher: DownloadUrlRefresher, private val appManagerWrapper: AppManagerWrapper, private val ageLimitGate: AgeLimitGate, private val devicePreconditions: DevicePreconditions, ) { suspend fun canEnqueue(appInstall: AppInstall, isAnUpdate: Boolean = false): Boolean { val hasUpdatedDownloadUrls = appInstall.type == Type.PWA || - appInstallDownloadUrlRefresher.updateDownloadUrls(appInstall, isAnUpdate) + downloadUrlRefresher.updateDownloadUrls(appInstall, isAnUpdate) val isDownloadAdded = hasUpdatedDownloadUrls && addDownload(appInstall) val isAgeLimitAllowed = isDownloadAdded && ageLimitGate.allow(appInstall) diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallDownloadUrlRefresher.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/DownloadUrlRefresher.kt similarity index 98% rename from app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallDownloadUrlRefresher.kt rename to app/src/main/java/foundation/e/apps/data/install/core/helper/DownloadUrlRefresher.kt index dcf84a42b..10bcc0bb5 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallDownloadUrlRefresher.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/DownloadUrlRefresher.kt @@ -33,7 +33,7 @@ import kotlinx.coroutines.CancellationException import timber.log.Timber import javax.inject.Inject -class AppInstallDownloadUrlRefresher @Inject constructor( +class DownloadUrlRefresher @Inject constructor( private val applicationRepository: ApplicationRepository, private val appInstallRepository: AppInstallRepository, private val appManagerWrapper: AppManagerWrapper, diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt index aa3993181..9b6a1b23d 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt @@ -24,7 +24,7 @@ import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.core.helper.AgeLimitGate import foundation.e.apps.data.install.core.helper.DevicePreconditions -import foundation.e.apps.data.install.core.helper.AppInstallDownloadUrlRefresher +import foundation.e.apps.data.install.core.helper.DownloadUrlRefresher import foundation.e.apps.data.install.core.helper.AppInstallPreEnqueueChecker import foundation.e.apps.domain.model.install.Status import io.mockk.coEvery @@ -39,7 +39,7 @@ import org.junit.Test @OptIn(ExperimentalCoroutinesApi::class) class AppInstallPreEnqueueCheckerTest { - private lateinit var appInstallDownloadUrlRefresher: AppInstallDownloadUrlRefresher + private lateinit var downloadUrlRefresher: DownloadUrlRefresher private lateinit var appManagerWrapper: AppManagerWrapper private lateinit var ageLimitGate: AgeLimitGate private lateinit var devicePreconditions: DevicePreconditions @@ -47,12 +47,12 @@ class AppInstallPreEnqueueCheckerTest { @Before fun setup() { - appInstallDownloadUrlRefresher = mockk(relaxed = true) + downloadUrlRefresher = mockk(relaxed = true) appManagerWrapper = mockk(relaxed = true) ageLimitGate = mockk(relaxed = true) devicePreconditions = mockk(relaxed = true) checker = AppInstallPreEnqueueChecker( - appInstallDownloadUrlRefresher, + downloadUrlRefresher, appManagerWrapper, ageLimitGate, devicePreconditions @@ -70,14 +70,14 @@ class AppInstallPreEnqueueCheckerTest { assertTrue(result) coVerify(exactly = 0) { - appInstallDownloadUrlRefresher.updateDownloadUrls(any(), any()) + downloadUrlRefresher.updateDownloadUrls(any(), any()) } } @Test fun canEnqueue_stopsWhenDownloadRefreshFails() = runTest { val appInstall = createNativeInstall() - coEvery { appInstallDownloadUrlRefresher.updateDownloadUrls(appInstall, false) } returns false + coEvery { downloadUrlRefresher.updateDownloadUrls(appInstall, false) } returns false val result = checker.canEnqueue(appInstall) @@ -90,7 +90,7 @@ class AppInstallPreEnqueueCheckerTest { @Test fun canEnqueue_stopsWhenAddingDownloadFails() = runTest { val appInstall = createNativeInstall() - coEvery { appInstallDownloadUrlRefresher.updateDownloadUrls(appInstall, false) } returns true + coEvery { downloadUrlRefresher.updateDownloadUrls(appInstall, false) } returns true coEvery { appManagerWrapper.addDownload(appInstall) } returns false val result = checker.canEnqueue(appInstall) @@ -103,7 +103,7 @@ class AppInstallPreEnqueueCheckerTest { @Test fun canEnqueue_stopsWhenAgeLimitRejectsInstall() = runTest { val appInstall = createNativeInstall() - coEvery { appInstallDownloadUrlRefresher.updateDownloadUrls(appInstall, false) } returns true + coEvery { downloadUrlRefresher.updateDownloadUrls(appInstall, false) } returns true coEvery { appManagerWrapper.addDownload(appInstall) } returns true coEvery { ageLimitGate.allow(appInstall) } returns false @@ -116,7 +116,7 @@ class AppInstallPreEnqueueCheckerTest { @Test fun canEnqueue_returnsTrueWhenAllChecksPass() = runTest { val appInstall = createNativeInstall() - coEvery { appInstallDownloadUrlRefresher.updateDownloadUrls(appInstall, false) } returns true + coEvery { downloadUrlRefresher.updateDownloadUrls(appInstall, false) } returns true coEvery { appManagerWrapper.addDownload(appInstall) } returns true coEvery { ageLimitGate.allow(appInstall) } returns true coEvery { devicePreconditions.canProceed(appInstall) } returns true diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt index 92cee0acb..37a74ab5c 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt @@ -33,7 +33,7 @@ import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager import foundation.e.apps.data.install.core.helper.AgeLimitGate import foundation.e.apps.data.install.core.helper.DevicePreconditions -import foundation.e.apps.data.install.core.helper.AppInstallDownloadUrlRefresher +import foundation.e.apps.data.install.core.helper.DownloadUrlRefresher import foundation.e.apps.data.install.core.helper.AppInstallPreEnqueueChecker import foundation.e.apps.data.install.core.helper.AppInstallStartCoordinator import foundation.e.apps.data.install.workmanager.InstallWorkManager @@ -74,7 +74,7 @@ class AppInstallStartCoordinatorTest { private lateinit var networkStatusChecker: NetworkStatusChecker private lateinit var appManager: AppManager private lateinit var devicePreconditions: DevicePreconditions - private lateinit var downloadUrlRefresher: AppInstallDownloadUrlRefresher + private lateinit var downloadUrlRefresher: DownloadUrlRefresher private lateinit var preflightChecker: AppInstallPreEnqueueChecker private lateinit var coordinator: AppInstallStartCoordinator @@ -94,7 +94,7 @@ class AppInstallStartCoordinatorTest { appManager = mockk(relaxed = true) coEvery { sessionRepository.awaitUser() } returns User.NO_GOOGLE coEvery { playStoreAuthStore.awaitAuthData() } returns null - downloadUrlRefresher = AppInstallDownloadUrlRefresher( + downloadUrlRefresher = DownloadUrlRefresher( applicationRepository, appInstallRepository, appManagerWrapper, diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallDownloadUrlRefresherTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/DownloadUrlRefresherTest.kt similarity index 96% rename from app/src/test/java/foundation/e/apps/installProcessor/AppInstallDownloadUrlRefresherTest.kt rename to app/src/test/java/foundation/e/apps/installProcessor/DownloadUrlRefresherTest.kt index 2d5afeac6..5602e02e3 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallDownloadUrlRefresherTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/DownloadUrlRefresherTest.kt @@ -27,7 +27,7 @@ import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.install.core.helper.AppInstallDownloadUrlRefresher +import foundation.e.apps.data.install.core.helper.DownloadUrlRefresher import foundation.e.apps.data.playstore.utils.GplayHttpRequestException import foundation.e.apps.domain.model.install.Status import io.mockk.coEvery @@ -43,13 +43,13 @@ import org.junit.Before import org.junit.Test @OptIn(ExperimentalCoroutinesApi::class) -class AppInstallDownloadUrlRefresherTest { +class DownloadUrlRefresherTest { private lateinit var applicationRepository: ApplicationRepository private lateinit var appInstallRepository: AppInstallRepository private lateinit var appManagerWrapper: AppManagerWrapper private lateinit var appEventDispatcher: FakeAppEventDispatcher private lateinit var appManager: AppManager - private lateinit var refresher: AppInstallDownloadUrlRefresher + private lateinit var refresher: DownloadUrlRefresher @Before fun setup() { @@ -58,7 +58,7 @@ class AppInstallDownloadUrlRefresherTest { appManagerWrapper = mockk(relaxed = true) appEventDispatcher = FakeAppEventDispatcher() appManager = mockk(relaxed = true) - refresher = AppInstallDownloadUrlRefresher( + refresher = DownloadUrlRefresher( applicationRepository, appInstallRepository, appManagerWrapper, -- GitLab From 6e9cabc795645c9c318494a7b8ce8cddc7f46035 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Wed, 18 Mar 2026 20:46:17 +0600 Subject: [PATCH 21/43] refactor: rename AppInstallPreEnqueueChecker to PreEnqueueChecker --- .../install/core/helper/AppInstallStartCoordinator.kt | 4 ++-- ...ppInstallPreEnqueueChecker.kt => PreEnqueueChecker.kt} | 2 +- .../installProcessor/AppInstallStartCoordinatorTest.kt | 6 +++--- ...lPreEnqueueCheckerTest.kt => PreEnqueueCheckerTest.kt} | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) rename app/src/main/java/foundation/e/apps/data/install/core/helper/{AppInstallPreEnqueueChecker.kt => PreEnqueueChecker.kt} (97%) rename app/src/test/java/foundation/e/apps/installProcessor/{AppInstallPreEnqueueCheckerTest.kt => PreEnqueueCheckerTest.kt} (95%) diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallStartCoordinator.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallStartCoordinator.kt index afb262d49..b9359f540 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallStartCoordinator.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallStartCoordinator.kt @@ -35,7 +35,7 @@ import javax.inject.Inject class AppInstallStartCoordinator @Inject constructor( @ApplicationContext private val context: Context, - private val appInstallPreEnqueueChecker: AppInstallPreEnqueueChecker, + private val preEnqueueChecker: PreEnqueueChecker, private val appManagerWrapper: AppManagerWrapper, private val sessionRepository: SessionRepository, private val playStoreAuthStore: PlayStoreAuthStore, @@ -82,7 +82,7 @@ class AppInstallStartCoordinator @Inject constructor( } suspend fun canEnqueue(appInstall: AppInstall, isAnUpdate: Boolean = false): Boolean { - return appInstallPreEnqueueChecker.canEnqueue(appInstall, isAnUpdate) + return preEnqueueChecker.canEnqueue(appInstall, isAnUpdate) } private suspend fun dispatchAnonymousPaidAppWarning(appInstall: AppInstall, isSystemApp: Boolean) { diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallPreEnqueueChecker.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/PreEnqueueChecker.kt similarity index 97% rename from app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallPreEnqueueChecker.kt rename to app/src/main/java/foundation/e/apps/data/install/core/helper/PreEnqueueChecker.kt index fd7efc0da..7d6cba8de 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallPreEnqueueChecker.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/PreEnqueueChecker.kt @@ -24,7 +24,7 @@ import foundation.e.apps.data.install.models.AppInstall import timber.log.Timber import javax.inject.Inject -class AppInstallPreEnqueueChecker @Inject constructor( +class PreEnqueueChecker @Inject constructor( private val downloadUrlRefresher: DownloadUrlRefresher, private val appManagerWrapper: AppManagerWrapper, private val ageLimitGate: AgeLimitGate, diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt index 37a74ab5c..33409f550 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt @@ -34,7 +34,7 @@ import foundation.e.apps.data.install.notification.StorageNotificationManager import foundation.e.apps.data.install.core.helper.AgeLimitGate import foundation.e.apps.data.install.core.helper.DevicePreconditions import foundation.e.apps.data.install.core.helper.DownloadUrlRefresher -import foundation.e.apps.data.install.core.helper.AppInstallPreEnqueueChecker +import foundation.e.apps.data.install.core.helper.PreEnqueueChecker import foundation.e.apps.data.install.core.helper.AppInstallStartCoordinator import foundation.e.apps.data.install.workmanager.InstallWorkManager import foundation.e.apps.data.install.wrapper.NetworkStatusChecker @@ -75,7 +75,7 @@ class AppInstallStartCoordinatorTest { private lateinit var appManager: AppManager private lateinit var devicePreconditions: DevicePreconditions private lateinit var downloadUrlRefresher: DownloadUrlRefresher - private lateinit var preflightChecker: AppInstallPreEnqueueChecker + private lateinit var preflightChecker: PreEnqueueChecker private lateinit var coordinator: AppInstallStartCoordinator @Before @@ -108,7 +108,7 @@ class AppInstallStartCoordinatorTest { storageSpaceChecker, networkStatusChecker ) - preflightChecker = AppInstallPreEnqueueChecker( + preflightChecker = PreEnqueueChecker( downloadUrlRefresher, appManagerWrapper, ageLimitGate, diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/PreEnqueueCheckerTest.kt similarity index 95% rename from app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt rename to app/src/test/java/foundation/e/apps/installProcessor/PreEnqueueCheckerTest.kt index 9b6a1b23d..bd46bdb07 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallPreEnqueueCheckerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/PreEnqueueCheckerTest.kt @@ -25,7 +25,7 @@ import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.core.helper.AgeLimitGate import foundation.e.apps.data.install.core.helper.DevicePreconditions import foundation.e.apps.data.install.core.helper.DownloadUrlRefresher -import foundation.e.apps.data.install.core.helper.AppInstallPreEnqueueChecker +import foundation.e.apps.data.install.core.helper.PreEnqueueChecker import foundation.e.apps.domain.model.install.Status import io.mockk.coEvery import io.mockk.coVerify @@ -38,12 +38,12 @@ import org.junit.Before import org.junit.Test @OptIn(ExperimentalCoroutinesApi::class) -class AppInstallPreEnqueueCheckerTest { +class PreEnqueueCheckerTest { private lateinit var downloadUrlRefresher: DownloadUrlRefresher private lateinit var appManagerWrapper: AppManagerWrapper private lateinit var ageLimitGate: AgeLimitGate private lateinit var devicePreconditions: DevicePreconditions - private lateinit var checker: AppInstallPreEnqueueChecker + private lateinit var checker: PreEnqueueChecker @Before fun setup() { @@ -51,7 +51,7 @@ class AppInstallPreEnqueueCheckerTest { appManagerWrapper = mockk(relaxed = true) ageLimitGate = mockk(relaxed = true) devicePreconditions = mockk(relaxed = true) - checker = AppInstallPreEnqueueChecker( + checker = PreEnqueueChecker( downloadUrlRefresher, appManagerWrapper, ageLimitGate, -- GitLab From 864241b59633b9538e63b8cd96fac7dd64e369f0 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Wed, 18 Mar 2026 20:52:38 +0600 Subject: [PATCH 22/43] refactor: rename AppInstallRequestFactory to InstallationRequest --- .../data/install/core/AppInstallProcessor.kt | 6 +++--- ...equestFactory.kt => InstallationRequest.kt} | 2 +- .../AppInstallProcessorTest.kt | 12 ++++++------ ...ctoryTest.kt => InstallationRequestTest.kt} | 18 +++++++++--------- 4 files changed, 19 insertions(+), 19 deletions(-) rename app/src/main/java/foundation/e/apps/data/install/core/helper/{AppInstallRequestFactory.kt => InstallationRequest.kt} (97%) rename app/src/test/java/foundation/e/apps/installProcessor/{AppInstallRequestFactoryTest.kt => InstallationRequestTest.kt} (85%) diff --git a/app/src/main/java/foundation/e/apps/data/install/core/AppInstallProcessor.kt b/app/src/main/java/foundation/e/apps/data/install/core/AppInstallProcessor.kt index 16bbbad04..17a4f594b 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/AppInstallProcessor.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/AppInstallProcessor.kt @@ -22,7 +22,7 @@ import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.install.AppInstallComponents import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.install.core.helper.AppInstallRequestFactory +import foundation.e.apps.data.install.core.helper.InstallationRequest import foundation.e.apps.data.install.core.helper.AppInstallStartCoordinator import foundation.e.apps.data.install.core.helper.AppInstallWorkRunner import foundation.e.apps.domain.model.install.Status @@ -32,7 +32,7 @@ class AppInstallProcessor @Inject constructor( private val appInstallComponents: AppInstallComponents, private val appInstallStartCoordinator: AppInstallStartCoordinator, private val appInstallWorkRunner: AppInstallWorkRunner, - private val appInstallRequestFactory: AppInstallRequestFactory, + private val installationRequest: InstallationRequest, ) { /** * creates [foundation.e.apps.data.install.models.AppInstall] from [foundation.e.apps.data.application.data.Application] and enqueues into WorkManager to run install process. @@ -44,7 +44,7 @@ class AppInstallProcessor @Inject constructor( application: Application, isAnUpdate: Boolean = false ): Boolean { - val appInstall = appInstallRequestFactory.create(application) + val appInstall = installationRequest.create(application) val isUpdate = isAnUpdate || application.status == Status.UPDATABLE || diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallRequestFactory.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationRequest.kt similarity index 97% rename from app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallRequestFactory.kt rename to app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationRequest.kt index 17aabba00..723aef3ff 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallRequestFactory.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationRequest.kt @@ -24,7 +24,7 @@ import foundation.e.apps.data.enums.Type import foundation.e.apps.data.install.models.AppInstall import javax.inject.Inject -class AppInstallRequestFactory @Inject constructor() { +class InstallationRequest @Inject constructor() { fun create(application: Application): AppInstall { val appInstall = AppInstall( application._id, diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt index f25697c84..efe1f446d 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt @@ -29,7 +29,7 @@ import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.core.AppInstallProcessor -import foundation.e.apps.data.install.core.helper.AppInstallRequestFactory +import foundation.e.apps.data.install.core.helper.InstallationRequest import foundation.e.apps.data.install.core.helper.AppInstallStartCoordinator import foundation.e.apps.data.install.core.helper.AppInstallWorkRunner import foundation.e.apps.util.MainCoroutineRule @@ -55,7 +55,7 @@ class AppInstallProcessorTest { private lateinit var appManagerWrapper: AppManagerWrapper private lateinit var appInstallProcessor: AppInstallProcessor - private lateinit var appInstallRequestFactory: AppInstallRequestFactory + private lateinit var installationRequest: InstallationRequest private lateinit var appInstallStartCoordinator: AppInstallStartCoordinator private lateinit var appInstallWorkRunner: AppInstallWorkRunner @@ -64,7 +64,7 @@ class AppInstallProcessorTest { appManagerWrapper = mockk(relaxed = true) val appInstallRepository = mockk(relaxed = true) val appInstallComponents = AppInstallComponents(appInstallRepository, appManagerWrapper) - appInstallRequestFactory = mockk(relaxed = true) + installationRequest = mockk(relaxed = true) appInstallStartCoordinator = mockk(relaxed = true) appInstallWorkRunner = mockk(relaxed = true) @@ -72,7 +72,7 @@ class AppInstallProcessorTest { appInstallComponents, appInstallStartCoordinator, appInstallWorkRunner, - appInstallRequestFactory + installationRequest ) } @@ -87,7 +87,7 @@ class AppInstallProcessorTest { type = Type.NATIVE ) val appInstall = AppInstall(id = "123", packageName = "com.example.app") - coEvery { appInstallRequestFactory.create(application) } returns appInstall + coEvery { installationRequest.create(application) } returns appInstall coEvery { appManagerWrapper.isFusedDownloadInstalled(appInstall) } returns false coEvery { appInstallStartCoordinator.enqueue( @@ -100,7 +100,7 @@ class AppInstallProcessorTest { val result = appInstallProcessor.initAppInstall(application) assertTrue(result) - coVerify { appInstallRequestFactory.create(application) } + coVerify { installationRequest.create(application) } coVerify { appInstallStartCoordinator.enqueue(appInstall, true, application.isSystemApp) } } diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallRequestFactoryTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationRequestTest.kt similarity index 85% rename from app/src/test/java/foundation/e/apps/installProcessor/AppInstallRequestFactoryTest.kt rename to app/src/test/java/foundation/e/apps/installProcessor/InstallationRequestTest.kt index 068e49d64..5a0d37c3c 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallRequestFactoryTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationRequestTest.kt @@ -22,19 +22,19 @@ import com.aurora.gplayapi.data.models.ContentRating import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Type -import foundation.e.apps.data.install.core.helper.AppInstallRequestFactory +import foundation.e.apps.data.install.core.helper.InstallationRequest import foundation.e.apps.domain.model.install.Status import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test -class AppInstallRequestFactoryTest { - private lateinit var factory: AppInstallRequestFactory +class InstallationRequestTest { + private lateinit var installationRequest: InstallationRequest @Before fun setup() { - factory = AppInstallRequestFactory() + installationRequest = InstallationRequest() } @Test @@ -53,7 +53,7 @@ class AppInstallRequestFactoryTest { originalSize = 2048L ) - val appInstall = factory.create(application) + val appInstall = installationRequest.create(application) assertEquals("123", appInstall.id) assertEquals(Source.PLAY_STORE, appInstall.source) @@ -73,7 +73,7 @@ class AppInstallRequestFactoryTest { val contentRating = ContentRating() val application = Application(contentRating = contentRating) - val appInstall = factory.create(application) + val appInstall = installationRequest.create(application) assertEquals(contentRating, appInstall.contentRating) } @@ -82,7 +82,7 @@ class AppInstallRequestFactoryTest { fun create_initializesDirectUrlForPwa() { val application = Application(type = Type.PWA, url = "https://example.com") - val appInstall = factory.create(application) + val appInstall = installationRequest.create(application) assertEquals(mutableListOf("https://example.com"), appInstall.downloadURLList) } @@ -91,7 +91,7 @@ class AppInstallRequestFactoryTest { fun create_initializesDirectUrlForSystemApp() { val application = Application(source = Source.SYSTEM_APP, url = "file://app.apk") - val appInstall = factory.create(application) + val appInstall = installationRequest.create(application) assertEquals(mutableListOf("file://app.apk"), appInstall.downloadURLList) } @@ -101,7 +101,7 @@ class AppInstallRequestFactoryTest { val application = Application(source = Source.PLAY_STORE, type = Type.NATIVE, url = "ignored") - val appInstall = factory.create(application) + val appInstall = installationRequest.create(application) assertTrue(appInstall.downloadURLList.isEmpty()) } -- GitLab From 8ff3bf712500ee9d874349498de53106c63dde2a Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Wed, 18 Mar 2026 20:57:36 +0600 Subject: [PATCH 23/43] refactor: rename AppInstallStartCoordinator to InstallationEnqueuer --- .../data/install/core/AppInstallProcessor.kt | 6 ++-- ...Coordinator.kt => InstallationEnqueuer.kt} | 2 +- .../AppInstallProcessorTest.kt | 16 +++++------ ...torTest.kt => InstallationEnqueuerTest.kt} | 28 +++++++++---------- 4 files changed, 26 insertions(+), 26 deletions(-) rename app/src/main/java/foundation/e/apps/data/install/core/helper/{AppInstallStartCoordinator.kt => InstallationEnqueuer.kt} (98%) rename app/src/test/java/foundation/e/apps/installProcessor/{AppInstallStartCoordinatorTest.kt => InstallationEnqueuerTest.kt} (93%) diff --git a/app/src/main/java/foundation/e/apps/data/install/core/AppInstallProcessor.kt b/app/src/main/java/foundation/e/apps/data/install/core/AppInstallProcessor.kt index 17a4f594b..a6b25d110 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/AppInstallProcessor.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/AppInstallProcessor.kt @@ -23,14 +23,14 @@ import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.install.AppInstallComponents import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.core.helper.InstallationRequest -import foundation.e.apps.data.install.core.helper.AppInstallStartCoordinator +import foundation.e.apps.data.install.core.helper.InstallationEnqueuer import foundation.e.apps.data.install.core.helper.AppInstallWorkRunner import foundation.e.apps.domain.model.install.Status import javax.inject.Inject class AppInstallProcessor @Inject constructor( private val appInstallComponents: AppInstallComponents, - private val appInstallStartCoordinator: AppInstallStartCoordinator, + private val installationEnqueuer: InstallationEnqueuer, private val appInstallWorkRunner: AppInstallWorkRunner, private val installationRequest: InstallationRequest, ) { @@ -65,7 +65,7 @@ class AppInstallProcessor @Inject constructor( isAnUpdate: Boolean = false, isSystemApp: Boolean = false ): Boolean { - return appInstallStartCoordinator.enqueue(appInstall, isAnUpdate, isSystemApp) + return installationEnqueuer.enqueue(appInstall, isAnUpdate, isSystemApp) } suspend fun processInstall( diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallStartCoordinator.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationEnqueuer.kt similarity index 98% rename from app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallStartCoordinator.kt rename to app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationEnqueuer.kt index b9359f540..c5d2340d9 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallStartCoordinator.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationEnqueuer.kt @@ -33,7 +33,7 @@ import kotlinx.coroutines.CancellationException import timber.log.Timber import javax.inject.Inject -class AppInstallStartCoordinator @Inject constructor( +class InstallationEnqueuer @Inject constructor( @ApplicationContext private val context: Context, private val preEnqueueChecker: PreEnqueueChecker, private val appManagerWrapper: AppManagerWrapper, diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt index efe1f446d..16a52a9e4 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt @@ -30,7 +30,7 @@ import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.core.AppInstallProcessor import foundation.e.apps.data.install.core.helper.InstallationRequest -import foundation.e.apps.data.install.core.helper.AppInstallStartCoordinator +import foundation.e.apps.data.install.core.helper.InstallationEnqueuer import foundation.e.apps.data.install.core.helper.AppInstallWorkRunner import foundation.e.apps.util.MainCoroutineRule import io.mockk.coEvery @@ -56,7 +56,7 @@ class AppInstallProcessorTest { private lateinit var appManagerWrapper: AppManagerWrapper private lateinit var appInstallProcessor: AppInstallProcessor private lateinit var installationRequest: InstallationRequest - private lateinit var appInstallStartCoordinator: AppInstallStartCoordinator + private lateinit var installationEnqueuer: InstallationEnqueuer private lateinit var appInstallWorkRunner: AppInstallWorkRunner @Before @@ -65,12 +65,12 @@ class AppInstallProcessorTest { val appInstallRepository = mockk(relaxed = true) val appInstallComponents = AppInstallComponents(appInstallRepository, appManagerWrapper) installationRequest = mockk(relaxed = true) - appInstallStartCoordinator = mockk(relaxed = true) + installationEnqueuer = mockk(relaxed = true) appInstallWorkRunner = mockk(relaxed = true) appInstallProcessor = AppInstallProcessor( appInstallComponents, - appInstallStartCoordinator, + installationEnqueuer, appInstallWorkRunner, installationRequest ) @@ -90,7 +90,7 @@ class AppInstallProcessorTest { coEvery { installationRequest.create(application) } returns appInstall coEvery { appManagerWrapper.isFusedDownloadInstalled(appInstall) } returns false coEvery { - appInstallStartCoordinator.enqueue( + installationEnqueuer.enqueue( appInstall, true, application.isSystemApp @@ -101,18 +101,18 @@ class AppInstallProcessorTest { assertTrue(result) coVerify { installationRequest.create(application) } - coVerify { appInstallStartCoordinator.enqueue(appInstall, true, application.isSystemApp) } + coVerify { installationEnqueuer.enqueue(appInstall, true, application.isSystemApp) } } @Test fun enqueueFusedDownload_delegatesResult() = runTest { val appInstall = AppInstall(id = "123", packageName = "com.example.app") - coEvery { appInstallStartCoordinator.enqueue(appInstall, true, true) } returns false + coEvery { installationEnqueuer.enqueue(appInstall, true, true) } returns false val result = appInstallProcessor.enqueueFusedDownload(appInstall, true, true) assertEquals(false, result) - coVerify { appInstallStartCoordinator.enqueue(appInstall, true, true) } + coVerify { installationEnqueuer.enqueue(appInstall, true, true) } } @Test diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt similarity index 93% rename from app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt rename to app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt index 33409f550..7439266b6 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallStartCoordinatorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt @@ -35,7 +35,7 @@ import foundation.e.apps.data.install.core.helper.AgeLimitGate import foundation.e.apps.data.install.core.helper.DevicePreconditions import foundation.e.apps.data.install.core.helper.DownloadUrlRefresher import foundation.e.apps.data.install.core.helper.PreEnqueueChecker -import foundation.e.apps.data.install.core.helper.AppInstallStartCoordinator +import foundation.e.apps.data.install.core.helper.InstallationEnqueuer import foundation.e.apps.data.install.workmanager.InstallWorkManager import foundation.e.apps.data.install.wrapper.NetworkStatusChecker import foundation.e.apps.data.install.wrapper.StorageSpaceChecker @@ -60,7 +60,7 @@ import org.junit.Before import org.junit.Test @OptIn(ExperimentalCoroutinesApi::class) -class AppInstallStartCoordinatorTest { +class InstallationEnqueuerTest { private lateinit var context: Context private lateinit var appManagerWrapper: AppManagerWrapper private lateinit var applicationRepository: ApplicationRepository @@ -76,7 +76,7 @@ class AppInstallStartCoordinatorTest { private lateinit var devicePreconditions: DevicePreconditions private lateinit var downloadUrlRefresher: DownloadUrlRefresher private lateinit var preflightChecker: PreEnqueueChecker - private lateinit var coordinator: AppInstallStartCoordinator + private lateinit var enqueuer: InstallationEnqueuer @Before fun setup() { @@ -114,7 +114,7 @@ class AppInstallStartCoordinatorTest { ageLimitGate, devicePreconditions ) - coordinator = AppInstallStartCoordinator( + enqueuer = InstallationEnqueuer( context, preflightChecker, appManagerWrapper, @@ -133,7 +133,7 @@ class AppInstallStartCoordinatorTest { every { networkStatusChecker.isNetworkAvailable() } returns true every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L - val result = coordinator.canEnqueue(appInstall) + val result = enqueuer.canEnqueue(appInstall) assertTrue(result) } @@ -147,7 +147,7 @@ class AppInstallStartCoordinatorTest { every { networkStatusChecker.isNetworkAvailable() } returns false every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L - val result = coordinator.canEnqueue(appInstall) + val result = enqueuer.canEnqueue(appInstall) assertFalse(result) coVerify { appManagerWrapper.installationIssue(appInstall) } @@ -162,7 +162,7 @@ class AppInstallStartCoordinatorTest { every { networkStatusChecker.isNetworkAvailable() } returns true every { storageSpaceChecker.spaceMissing(appInstall) } returns 100L - val result = coordinator.canEnqueue(appInstall) + val result = enqueuer.canEnqueue(appInstall) assertFalse(result) verify { storageNotificationManager.showNotEnoughSpaceNotification(appInstall) } @@ -175,7 +175,7 @@ class AppInstallStartCoordinatorTest { coEvery { appManagerWrapper.addDownload(appInstall) } returns false - val result = coordinator.canEnqueue(appInstall) + val result = enqueuer.canEnqueue(appInstall) assertFalse(result) coVerify(exactly = 0) { ageLimitGate.allow(any()) } @@ -197,7 +197,7 @@ class AppInstallStartCoordinatorTest { every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L justRun { InstallWorkManager.enqueueWork(any(), any(), any()) } - val result = coordinator.enqueue(appInstall) + val result = enqueuer.enqueue(appInstall) assertTrue(result) assertTrue(appEventDispatcher.events.any { @@ -221,7 +221,7 @@ class AppInstallStartCoordinatorTest { ) } throws InternalException.AppNotPurchased() - val result = coordinator.canEnqueue(appInstall) + val result = enqueuer.canEnqueue(appInstall) assertFalse(result) assertTrue(appEventDispatcher.events.any { it is AppEvent.AppRestrictedOrUnavailable }) @@ -240,7 +240,7 @@ class AppInstallStartCoordinatorTest { ) } throws InternalException.AppNotPurchased() - val result = coordinator.canEnqueue(appInstall) + val result = enqueuer.canEnqueue(appInstall) assertFalse(result) coVerify { appManagerWrapper.addFusedDownloadPurchaseNeeded(appInstall) } @@ -259,7 +259,7 @@ class AppInstallStartCoordinatorTest { ) } throws GplayHttpRequestException(403, "forbidden") - val result = coordinator.canEnqueue(appInstall) + val result = enqueuer.canEnqueue(appInstall) assertFalse(result) assertTrue(appEventDispatcher.events.none { it is AppEvent.UpdateEvent }) @@ -280,7 +280,7 @@ class AppInstallStartCoordinatorTest { ) } throws GplayHttpRequestException(403, "forbidden") - val result = coordinator.canEnqueue(appInstall, true) + val result = enqueuer.canEnqueue(appInstall, true) assertFalse(result) assertTrue(appEventDispatcher.events.any { it is AppEvent.UpdateEvent }) @@ -301,7 +301,7 @@ class AppInstallStartCoordinatorTest { ) } throws IllegalStateException("boom") - val result = coordinator.canEnqueue(appInstall) + val result = enqueuer.canEnqueue(appInstall) assertFalse(result) coVerify { appInstallRepository.addDownload(appInstall) } -- GitLab From d9c2521e9081bdac115e61a3a6c2740d6aa3f8ec Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Wed, 18 Mar 2026 21:04:44 +0600 Subject: [PATCH 24/43] refactor: rename AppInstallProcessor to AppInstallationFacade --- ...lProcessor.kt => AppInstallationFacade.kt} | 2 +- .../data/install/updates/UpdatesWorker.kt | 6 +- .../install/workmanager/InstallAppWorker.kt | 6 +- .../domain/install/InstallAppByIdUseCase.kt | 6 +- .../e/apps/ui/MainActivityViewModel.kt | 8 +- .../data/install/updates/UpdatesWorkerTest.kt | 95 +++++++++---------- .../install/InstallAppByIdUseCaseTest.kt | 14 +-- ...orTest.kt => AppInstallationFacadeTest.kt} | 14 +-- .../installProcessor/InstallAppWorkerTest.kt | 18 ++-- 9 files changed, 84 insertions(+), 85 deletions(-) rename app/src/main/java/foundation/e/apps/data/install/core/{AppInstallProcessor.kt => AppInstallationFacade.kt} (98%) rename app/src/test/java/foundation/e/apps/installProcessor/{AppInstallProcessorTest.kt => AppInstallationFacadeTest.kt} (90%) diff --git a/app/src/main/java/foundation/e/apps/data/install/core/AppInstallProcessor.kt b/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt similarity index 98% rename from app/src/main/java/foundation/e/apps/data/install/core/AppInstallProcessor.kt rename to app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt index a6b25d110..141ea8a9a 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/AppInstallProcessor.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt @@ -28,7 +28,7 @@ import foundation.e.apps.data.install.core.helper.AppInstallWorkRunner import foundation.e.apps.domain.model.install.Status import javax.inject.Inject -class AppInstallProcessor @Inject constructor( +class AppInstallationFacade @Inject constructor( private val appInstallComponents: AppInstallComponents, private val installationEnqueuer: InstallationEnqueuer, private val appInstallWorkRunner: AppInstallWorkRunner, diff --git a/app/src/main/java/foundation/e/apps/data/install/updates/UpdatesWorker.kt b/app/src/main/java/foundation/e/apps/data/install/updates/UpdatesWorker.kt index 5af1859a2..ffd2f35a3 100644 --- a/app/src/main/java/foundation/e/apps/data/install/updates/UpdatesWorker.kt +++ b/app/src/main/java/foundation/e/apps/data/install/updates/UpdatesWorker.kt @@ -20,7 +20,7 @@ import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.event.EventBus import foundation.e.apps.data.gitlab.SystemAppsUpdatesRepository -import foundation.e.apps.data.install.core.AppInstallProcessor +import foundation.e.apps.data.install.core.AppInstallationFacade import foundation.e.apps.data.updates.UpdatesManagerRepository import foundation.e.apps.domain.model.User import foundation.e.apps.domain.preferences.AppPreferencesRepository @@ -42,7 +42,7 @@ class UpdatesWorker @AssistedInject constructor( private val sessionRepository: SessionRepository, private val appPreferencesRepository: AppPreferencesRepository, private val playStoreAuthManager: PlayStoreAuthManager, - private val appInstallProcessor: AppInstallProcessor, + private val appInstallationFacade: AppInstallationFacade, ) : CoroutineWorker(context, params) { companion object { @@ -220,7 +220,7 @@ class UpdatesWorker @AssistedInject constructor( response.add(Pair(fusedApp, false)) continue } - val status = appInstallProcessor.initAppInstall(fusedApp, true) + val status = appInstallationFacade.initAppInstall(fusedApp, true) response.add(Pair(fusedApp, status)) } return response 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 97c787556..b095fb55f 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 @@ -32,14 +32,14 @@ import androidx.work.WorkerParameters import dagger.assisted.Assisted import dagger.assisted.AssistedInject import foundation.e.apps.R -import foundation.e.apps.data.install.core.AppInstallProcessor +import foundation.e.apps.data.install.core.AppInstallationFacade import java.util.concurrent.atomic.AtomicInteger @HiltWorker class InstallAppWorker @AssistedInject constructor( @Assisted private val context: Context, @Assisted private val params: WorkerParameters, - private val appInstallProcessor: AppInstallProcessor + private val appInstallationFacade: AppInstallationFacade ) : CoroutineWorker(context, params) { companion object { @@ -65,7 +65,7 @@ class InstallAppWorker @AssistedInject constructor( } val isPackageUpdate = params.inputData.getBoolean(IS_UPDATE_WORK, false) - val response = appInstallProcessor.processInstall(fusedDownloadId, isPackageUpdate) { title -> + val response = appInstallationFacade.processInstall(fusedDownloadId, isPackageUpdate) { title -> setForeground(createForegroundInfo("${context.getString(R.string.installing)} $title")) } diff --git a/app/src/main/java/foundation/e/apps/domain/install/InstallAppByIdUseCase.kt b/app/src/main/java/foundation/e/apps/domain/install/InstallAppByIdUseCase.kt index 43cfa0385..ea3a11702 100644 --- a/app/src/main/java/foundation/e/apps/domain/install/InstallAppByIdUseCase.kt +++ b/app/src/main/java/foundation/e/apps/domain/install/InstallAppByIdUseCase.kt @@ -21,7 +21,7 @@ package foundation.e.apps.domain.install import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.Source import foundation.e.apps.data.install.AppInstallRepository -import foundation.e.apps.data.install.core.AppInstallProcessor +import foundation.e.apps.data.install.core.AppInstallationFacade import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PwaManager import foundation.e.apps.domain.model.install.Status @@ -33,7 +33,7 @@ import javax.inject.Inject class InstallAppByIdUseCase @Inject constructor( private val getAppDetailsUseCase: GetAppDetailsUseCase, private val appLoungePackageManager: AppLoungePackageManager, - private val appInstallProcessor: AppInstallProcessor, + private val appInstallationFacade: AppInstallationFacade, private val appInstallRepository: AppInstallRepository, private val pwaManager: PwaManager ) { @@ -49,7 +49,7 @@ class InstallAppByIdUseCase @Inject constructor( return try { val app: Application = getAppDetailsUseCase(packageName) - appInstallProcessor.initAppInstall(app, isAnUpdate = false) + appInstallationFacade.initAppInstall(app, isAnUpdate = false) appInstallRepository.getDownloadFlowById(app._id).takeWhile { _status.value = it?.status diff --git a/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt b/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt index 3298a7b05..e596bae11 100644 --- a/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt @@ -36,7 +36,7 @@ import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PwaManager -import foundation.e.apps.data.install.core.AppInstallProcessor +import foundation.e.apps.data.install.core.AppInstallationFacade import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.system.NetworkStatusManager import foundation.e.apps.domain.application.ApplicationDomain @@ -56,7 +56,7 @@ class MainActivityViewModel @Inject constructor( private val appManagerWrapper: AppManagerWrapper, private val appLoungePackageManager: AppLoungePackageManager, private val pwaManager: PwaManager, - private val appInstallProcessor: AppInstallProcessor, + private val appInstallationFacade: AppInstallationFacade, private val sessionManager: MainActivitySessionManager, private val startupCoordinator: MainActivityStartupCoordinator, ) : ViewModel() { @@ -295,7 +295,7 @@ class MainActivityViewModel @Inject constructor( fun getApplication(app: Application) { viewModelScope.launch(Dispatchers.IO) { - appInstallProcessor.initAppInstall(app) + appInstallationFacade.initAppInstall(app) } } @@ -307,7 +307,7 @@ class MainActivityViewModel @Inject constructor( val fusedDownload = appManagerWrapper.getFusedDownload(packageName = packageName) val authData = sessionManager.awaitAuthData() if (authData?.isAnonymous != true) { - appInstallProcessor.enqueueFusedDownload(fusedDownload) + appInstallationFacade.enqueueFusedDownload(fusedDownload) return fusedDownload } diff --git a/app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkerTest.kt b/app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkerTest.kt index f3e07e015..8ff3506f9 100644 --- a/app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkerTest.kt +++ b/app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkerTest.kt @@ -41,19 +41,18 @@ import foundation.e.apps.data.blockedApps.BlockedAppRepository import foundation.e.apps.data.enums.FilterLevel import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.enums.Source -import foundation.e.apps.domain.model.install.Status import foundation.e.apps.data.gitlab.SystemAppsUpdatesRepository -import foundation.e.apps.data.install.core.AppInstallProcessor +import foundation.e.apps.data.install.core.AppInstallationFacade import foundation.e.apps.data.updates.UpdatesManagerRepository import foundation.e.apps.domain.model.LoginState import foundation.e.apps.domain.model.User +import foundation.e.apps.domain.model.install.Status import foundation.e.apps.domain.preferences.AppPreferencesRepository import foundation.e.apps.domain.preferences.SessionRepository import foundation.e.apps.login.PlayStoreAuthManager import io.mockk.coEvery import io.mockk.coVerify import io.mockk.spyk -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.test.runTest @@ -87,7 +86,7 @@ class UpdatesWorkerTest { val updatesManagerRepository = mock() val appLoungeDataStore = createDataStore() val playStoreAuthManager = mock() - val appInstallProcessor = mock() + val appInstallationFacade = mock() val blockedAppRepository = mock() val systemAppsUpdatesRepository = mock() val appPreferencesRepository = createAppPreferencesRepository( @@ -141,7 +140,7 @@ class UpdatesWorkerTest { updatesManagerRepository, appLoungeDataStore, playStoreAuthManager, - appInstallProcessor, + appInstallationFacade, blockedAppRepository, systemAppsUpdatesRepository ) @@ -163,7 +162,7 @@ class UpdatesWorkerTest { val updatesManagerRepository = mock() val appLoungeDataStore = createDataStore() val playStoreAuthManager = mock() - val appInstallProcessor = mock() + val appInstallationFacade = mock() val blockedAppRepository = mock() val systemAppsUpdatesRepository = mock() val authData = AuthData(email = "user@example.com") @@ -218,7 +217,7 @@ class UpdatesWorkerTest { updatesManagerRepository, appLoungeDataStore, playStoreAuthManager, - appInstallProcessor, + appInstallationFacade, blockedAppRepository, systemAppsUpdatesRepository ) @@ -229,7 +228,7 @@ class UpdatesWorkerTest { verify(updatesManagerRepository, times(UpdatesWorker.MAX_RETRY_COUNT.plus(1))).getUpdates() verify(playStoreAuthManager, times(UpdatesWorker.MAX_RETRY_COUNT.plus(1))).getValidatedAuthData() verify(updatesManagerRepository, never()).getUpdatesOSS() - verify(appInstallProcessor, never()).initAppInstall(any(), any()) + verify(appInstallationFacade, never()).initAppInstall(any(), any()) } @Test @@ -239,7 +238,7 @@ class UpdatesWorkerTest { val updatesManagerRepository = mock() val appLoungeDataStore = createDataStore() val playStoreAuthManager = createPlayStoreAuthManager() - val appInstallProcessor = mock() + val appInstallationFacade = mock() val blockedAppRepository = mock() val systemAppsUpdatesRepository = mock() val notificationManager = mock() @@ -260,7 +259,7 @@ class UpdatesWorkerTest { updatesManagerRepository, appLoungeDataStore, playStoreAuthManager, - appInstallProcessor, + appInstallationFacade, blockedAppRepository, systemAppsUpdatesRepository ), @@ -287,7 +286,7 @@ class UpdatesWorkerTest { val updatesManagerRepository = mock() val appLoungeDataStore = createDataStore() val playStoreAuthManager = createPlayStoreAuthManager() - val appInstallProcessor = mock() + val appInstallationFacade = mock() val blockedAppRepository = mock() val systemAppsUpdatesRepository = mock() @@ -297,7 +296,7 @@ class UpdatesWorkerTest { updatesManagerRepository, appLoungeDataStore, playStoreAuthManager, - appInstallProcessor, + appInstallationFacade, blockedAppRepository, systemAppsUpdatesRepository ) @@ -313,7 +312,7 @@ class UpdatesWorkerTest { val updatesManagerRepository = mock() val appLoungeDataStore = createDataStore() val playStoreAuthManager = createPlayStoreAuthManager() - val appInstallProcessor = mock() + val appInstallationFacade = mock() val blockedAppRepository = mock() val systemAppsUpdatesRepository = mock() val worker = createWorker( @@ -322,7 +321,7 @@ class UpdatesWorkerTest { updatesManagerRepository, appLoungeDataStore, playStoreAuthManager, - appInstallProcessor, + appInstallationFacade, blockedAppRepository, systemAppsUpdatesRepository ) @@ -341,7 +340,7 @@ class UpdatesWorkerTest { val updatesManagerRepository = mock() val appLoungeDataStore = createDataStore() val playStoreAuthManager = createPlayStoreAuthManager() - val appInstallProcessor = mock() + val appInstallationFacade = mock() val blockedAppRepository = mock() val systemAppsUpdatesRepository = mock() val worker = createWorker( @@ -350,7 +349,7 @@ class UpdatesWorkerTest { updatesManagerRepository, appLoungeDataStore, playStoreAuthManager, - appInstallProcessor, + appInstallationFacade, blockedAppRepository, systemAppsUpdatesRepository ) @@ -389,7 +388,7 @@ class UpdatesWorkerTest { val updatesManagerRepository = mock() val appLoungeDataStore = createDataStore() val playStoreAuthManager = createPlayStoreAuthManager() - val appInstallProcessor = mock() + val appInstallationFacade = mock() val blockedAppRepository = mock() val systemAppsUpdatesRepository = mock() @@ -399,7 +398,7 @@ class UpdatesWorkerTest { updatesManagerRepository, appLoungeDataStore, playStoreAuthManager, - appInstallProcessor, + appInstallationFacade, blockedAppRepository, systemAppsUpdatesRepository ) @@ -421,7 +420,7 @@ class UpdatesWorkerTest { val updatesManagerRepository = mock() val sessionRepository = mock() val playStoreAuthManager = mock() - val appInstallProcessor = mock() + val appInstallationFacade = mock() val blockedAppRepository = mock() val systemAppsUpdatesRepository = mock() @@ -440,7 +439,7 @@ class UpdatesWorkerTest { updatesManagerRepository, sessionRepository, playStoreAuthManager, - appInstallProcessor, + appInstallationFacade, blockedAppRepository, systemAppsUpdatesRepository, appPreferencesRepository @@ -461,7 +460,7 @@ class UpdatesWorkerTest { val updatesManagerRepository = mock() val sessionRepository = mock() val playStoreAuthManager = mock() - val appInstallProcessor = mock() + val appInstallationFacade = mock() val blockedAppRepository = mock() val systemAppsUpdatesRepository = mock() val appPreferencesRepository = createAppPreferencesRepository( @@ -485,7 +484,7 @@ class UpdatesWorkerTest { updatesManagerRepository, sessionRepository, playStoreAuthManager, - appInstallProcessor, + appInstallationFacade, blockedAppRepository, systemAppsUpdatesRepository, appPreferencesRepository @@ -510,7 +509,7 @@ class UpdatesWorkerTest { val updatesManagerRepository = mock() val sessionRepository = mock() val playStoreAuthManager = mock() - val appInstallProcessor = mock() + val appInstallationFacade = mock() val blockedAppRepository = mock() val systemAppsUpdatesRepository = mock() val appPreferencesRepository = createAppPreferencesRepository( @@ -560,7 +559,7 @@ class UpdatesWorkerTest { ) whenever(systemAppsUpdatesRepository.fetchUpdatableSystemApps(true)) .thenReturn(ResultSupreme.Success(Unit)) - whenever(appInstallProcessor.initAppInstall(any(), any())).thenReturn(true) + whenever(appInstallationFacade.initAppInstall(any(), any())).thenReturn(true) val worker = createWorker( workerContext, @@ -568,7 +567,7 @@ class UpdatesWorkerTest { updatesManagerRepository, sessionRepository, playStoreAuthManager, - appInstallProcessor, + appInstallationFacade, blockedAppRepository, systemAppsUpdatesRepository, appPreferencesRepository @@ -577,7 +576,7 @@ class UpdatesWorkerTest { val result = worker.doWork() assertThat(result).isEqualTo(androidx.work.ListenableWorker.Result.success()) - verify(appInstallProcessor).initAppInstall(any(), any()) + verify(appInstallationFacade).initAppInstall(any(), any()) } @Test @@ -592,7 +591,7 @@ class UpdatesWorkerTest { val updatesManagerRepository = mock() val sessionRepository = mock() val playStoreAuthManager = mock() - val appInstallProcessor = mock() + val appInstallationFacade = mock() val blockedAppRepository = mock() val systemAppsUpdatesRepository = mock() @@ -644,7 +643,7 @@ class UpdatesWorkerTest { updatesManagerRepository, sessionRepository, playStoreAuthManager, - appInstallProcessor, + appInstallationFacade, blockedAppRepository, systemAppsUpdatesRepository ) @@ -652,7 +651,7 @@ class UpdatesWorkerTest { val result = worker.doWork() assertThat(result).isEqualTo(androidx.work.ListenableWorker.Result.failure()) - verify(appInstallProcessor, never()).initAppInstall(any(), any()) + verify(appInstallationFacade, never()).initAppInstall(any(), any()) } @@ -668,7 +667,7 @@ class UpdatesWorkerTest { val updatesManagerRepository = mock() val appLoungeDataStore = createDataStore() val playStoreAuthManager = createPlayStoreAuthManager() - val appInstallProcessor = mock() + val appInstallationFacade = mock() val blockedAppRepository = mock() val systemAppsUpdatesRepository = mock() @@ -696,7 +695,7 @@ class UpdatesWorkerTest { updatesManagerRepository, appLoungeDataStore, playStoreAuthManager, - appInstallProcessor, + appInstallationFacade, blockedAppRepository, systemAppsUpdatesRepository ) @@ -722,7 +721,7 @@ class UpdatesWorkerTest { val updatesManagerRepository = mock() val appLoungeDataStore = createDataStore() val playStoreAuthManager = createPlayStoreAuthManager() - val appInstallProcessor = mock() + val appInstallationFacade = mock() val blockedAppRepository = mock() val systemAppsUpdatesRepository = mock() @@ -750,7 +749,7 @@ class UpdatesWorkerTest { updatesManagerRepository, appLoungeDataStore, playStoreAuthManager, - appInstallProcessor, + appInstallationFacade, blockedAppRepository, systemAppsUpdatesRepository ) @@ -773,7 +772,7 @@ class UpdatesWorkerTest { val playStoreAuthManager = createPlayStoreAuthManager( ResultSupreme.Success(AuthData(email = "anon@example.com", isAnonymous = true)) ) - val appInstallProcessor = mock() + val appInstallationFacade = mock() val blockedAppRepository = mock() val systemAppsUpdatesRepository = mock() @@ -783,14 +782,14 @@ class UpdatesWorkerTest { updatesManagerRepository, appLoungeDataStore, playStoreAuthManager, - appInstallProcessor, + appInstallationFacade, blockedAppRepository, systemAppsUpdatesRepository ) val paidApp = Application(name = "Paid", isFree = false) val freeApp = Application(name = "Free", isFree = true) - whenever(appInstallProcessor.initAppInstall(freeApp, true)).thenReturn(true) + whenever(appInstallationFacade.initAppInstall(freeApp, true)).thenReturn(true) val result = worker.startUpdateProcess(listOf(paidApp, freeApp)) @@ -798,8 +797,8 @@ class UpdatesWorkerTest { Pair(paidApp, false), Pair(freeApp, true) ) - verify(appInstallProcessor, times(1)).initAppInstall(freeApp, true) - verify(appInstallProcessor, times(0)).initAppInstall(paidApp, true) + verify(appInstallationFacade, times(1)).initAppInstall(freeApp, true) + verify(appInstallationFacade, times(0)).initAppInstall(paidApp, true) } @Test @@ -810,7 +809,7 @@ class UpdatesWorkerTest { val appLoungeDataStore = createDataStore() val authData = AuthData(email = "user@example.com", isAnonymous = false) val playStoreAuthManager = createPlayStoreAuthManager(ResultSupreme.Success(authData)) - val appInstallProcessor = mock() + val appInstallationFacade = mock() val blockedAppRepository = mock() val systemAppsUpdatesRepository = mock() @@ -820,18 +819,18 @@ class UpdatesWorkerTest { updatesManagerRepository, appLoungeDataStore, playStoreAuthManager, - appInstallProcessor, + appInstallationFacade, blockedAppRepository, systemAppsUpdatesRepository ) val paidApp = Application(name = "Paid", isFree = false) - whenever(appInstallProcessor.initAppInstall(paidApp, true)).thenReturn(false) + whenever(appInstallationFacade.initAppInstall(paidApp, true)).thenReturn(false) val result = worker.startUpdateProcess(listOf(paidApp)) assertThat(result).containsExactly(Pair(paidApp, false)) - verify(appInstallProcessor, times(1)).initAppInstall(paidApp, true) + verify(appInstallationFacade, times(1)).initAppInstall(paidApp, true) } @Test @@ -841,7 +840,7 @@ class UpdatesWorkerTest { val updatesManagerRepository = mock() val appLoungeDataStore = createDataStore() val playStoreAuthManager = createPlayStoreAuthManager() - val appInstallProcessor = mock() + val appInstallationFacade = mock() val blockedAppRepository = mock() val systemAppsUpdatesRepository = mock() @@ -856,7 +855,7 @@ class UpdatesWorkerTest { updatesManagerRepository, appLoungeDataStore, playStoreAuthManager, - appInstallProcessor, + appInstallationFacade, blockedAppRepository, systemAppsUpdatesRepository ) @@ -889,7 +888,7 @@ class UpdatesWorkerTest { val updatesManagerRepository = mock() val appLoungeDataStore = createDataStore() val playStoreAuthManager = createPlayStoreAuthManager() - val appInstallProcessor = mock() + val appInstallationFacade = mock() val blockedAppRepository = mock() val systemAppsUpdatesRepository = mock() @@ -904,7 +903,7 @@ class UpdatesWorkerTest { updatesManagerRepository, appLoungeDataStore, playStoreAuthManager, - appInstallProcessor, + appInstallationFacade, blockedAppRepository, systemAppsUpdatesRepository ) @@ -944,7 +943,7 @@ class UpdatesWorkerTest { updatesManagerRepository: UpdatesManagerRepository, sessionRepository: SessionRepository, playStoreAuthManager: PlayStoreAuthManager, - appInstallProcessor: AppInstallProcessor, + appInstallationFacade: AppInstallationFacade, blockedAppRepository: BlockedAppRepository, systemAppsUpdatesRepository: SystemAppsUpdatesRepository, appPreferencesRepository: AppPreferencesRepository = createAppPreferencesRepository(), @@ -958,7 +957,7 @@ class UpdatesWorkerTest { sessionRepository, appPreferencesRepository, playStoreAuthManager, - appInstallProcessor + appInstallationFacade ) } diff --git a/app/src/test/java/foundation/e/apps/domain/install/InstallAppByIdUseCaseTest.kt b/app/src/test/java/foundation/e/apps/domain/install/InstallAppByIdUseCaseTest.kt index e6ea48f4b..b39ff53bd 100644 --- a/app/src/test/java/foundation/e/apps/domain/install/InstallAppByIdUseCaseTest.kt +++ b/app/src/test/java/foundation/e/apps/domain/install/InstallAppByIdUseCaseTest.kt @@ -21,10 +21,10 @@ package foundation.e.apps.domain.install import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.Source import foundation.e.apps.data.install.AppInstallRepository +import foundation.e.apps.data.install.core.AppInstallationFacade import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PwaManager -import foundation.e.apps.data.install.workmanager.AppInstallProcessor import foundation.e.apps.domain.model.install.Status import io.mockk.coEvery import io.mockk.coVerify @@ -42,7 +42,7 @@ class InstallAppByIdUseCaseTest { private val getAppDetailsUseCase: GetAppDetailsUseCase = mockk() private val appLoungePackageManager: AppLoungePackageManager = mockk() - private val appInstallProcessor: AppInstallProcessor = mockk(relaxed = true) + private val appInstallationFacade: AppInstallationFacade = mockk(relaxed = true) private val appInstallRepository: AppInstallRepository = mockk() private val pwaManager: PwaManager = mockk() @@ -53,7 +53,7 @@ class InstallAppByIdUseCaseTest { useCase = InstallAppByIdUseCase( getAppDetailsUseCase, appLoungePackageManager, - appInstallProcessor, + appInstallationFacade, appInstallRepository, pwaManager ) @@ -67,7 +67,7 @@ class InstallAppByIdUseCaseTest { assertEquals(Status.INSTALLED, result) coVerify(exactly = 0) { getAppDetailsUseCase("pkg") } - coVerify(exactly = 0) { appInstallProcessor.initAppInstall(any(), any()) } + coVerify(exactly = 0) { appInstallationFacade.initAppInstall(any(), any()) } } @Test @@ -108,7 +108,7 @@ class InstallAppByIdUseCaseTest { val result = useCase("pkg") assertEquals(Status.INSTALLED, result) - coVerify { appInstallProcessor.initAppInstall(app, isAnUpdate = false) } + coVerify { appInstallationFacade.initAppInstall(app, isAnUpdate = false) } verify { pwaManager.getPwaStatus(app) } } @@ -123,7 +123,7 @@ class InstallAppByIdUseCaseTest { val result = useCase("pkg") assertEquals(Status.INSTALLATION_ISSUE, result) - coVerify { appInstallProcessor.initAppInstall(app, isAnUpdate = false) } + coVerify { appInstallationFacade.initAppInstall(app, isAnUpdate = false) } } @Test @@ -141,7 +141,7 @@ class InstallAppByIdUseCaseTest { val result = useCase("pkg") assertEquals(Status.BLOCKED, result) - coVerify { appInstallProcessor.initAppInstall(app, isAnUpdate = false) } + coVerify { appInstallationFacade.initAppInstall(app, isAnUpdate = false) } } private fun applicationFixture(id: String, packageName: String, source: Source): Application { diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt similarity index 90% rename from app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt rename to app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt index 16a52a9e4..51e15d619 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt @@ -28,7 +28,7 @@ import foundation.e.apps.data.install.AppInstallComponents import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.install.core.AppInstallProcessor +import foundation.e.apps.data.install.core.AppInstallationFacade import foundation.e.apps.data.install.core.helper.InstallationRequest import foundation.e.apps.data.install.core.helper.InstallationEnqueuer import foundation.e.apps.data.install.core.helper.AppInstallWorkRunner @@ -45,7 +45,7 @@ import org.junit.Rule import org.junit.Test @OptIn(ExperimentalCoroutinesApi::class) -class AppInstallProcessorTest { +class AppInstallationFacadeTest { @Rule @JvmField val instantExecutorRule = InstantTaskExecutorRule() @@ -54,7 +54,7 @@ class AppInstallProcessorTest { var mainCoroutineRule = MainCoroutineRule() private lateinit var appManagerWrapper: AppManagerWrapper - private lateinit var appInstallProcessor: AppInstallProcessor + private lateinit var appInstallationFacade: AppInstallationFacade private lateinit var installationRequest: InstallationRequest private lateinit var installationEnqueuer: InstallationEnqueuer private lateinit var appInstallWorkRunner: AppInstallWorkRunner @@ -68,7 +68,7 @@ class AppInstallProcessorTest { installationEnqueuer = mockk(relaxed = true) appInstallWorkRunner = mockk(relaxed = true) - appInstallProcessor = AppInstallProcessor( + appInstallationFacade = AppInstallationFacade( appInstallComponents, installationEnqueuer, appInstallWorkRunner, @@ -97,7 +97,7 @@ class AppInstallProcessorTest { ) } returns true - val result = appInstallProcessor.initAppInstall(application) + val result = appInstallationFacade.initAppInstall(application) assertTrue(result) coVerify { installationRequest.create(application) } @@ -109,7 +109,7 @@ class AppInstallProcessorTest { val appInstall = AppInstall(id = "123", packageName = "com.example.app") coEvery { installationEnqueuer.enqueue(appInstall, true, true) } returns false - val result = appInstallProcessor.enqueueFusedDownload(appInstall, true, true) + val result = appInstallationFacade.enqueueFusedDownload(appInstall, true, true) assertEquals(false, result) coVerify { installationEnqueuer.enqueue(appInstall, true, true) } @@ -121,7 +121,7 @@ class AppInstallProcessorTest { appInstallWorkRunner.processInstall("123", false, any()) } returns Result.success(ResultStatus.OK) - val result = appInstallProcessor.processInstall("123", false) { + val result = appInstallationFacade.processInstall("123", false) { // _ignored_ } diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallAppWorkerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallAppWorkerTest.kt index 8efe9bf3d..953424eb5 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/InstallAppWorkerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallAppWorkerTest.kt @@ -23,7 +23,7 @@ import androidx.test.core.app.ApplicationProvider import androidx.work.Data import com.google.common.truth.Truth.assertThat import foundation.e.apps.data.enums.ResultStatus -import foundation.e.apps.data.install.core.AppInstallProcessor +import foundation.e.apps.data.install.core.AppInstallationFacade import foundation.e.apps.data.install.workmanager.InstallAppWorker import io.mockk.coEvery import io.mockk.coVerify @@ -41,11 +41,11 @@ import org.robolectric.annotation.Config @Config(sdk = [Build.VERSION_CODES.R]) @OptIn(ExperimentalCoroutinesApi::class) class InstallAppWorkerTest { - private lateinit var appInstallProcessor: AppInstallProcessor + private lateinit var appInstallationFacade: AppInstallationFacade @Before fun setup() { - appInstallProcessor = mockk(relaxed = true) + appInstallationFacade = mockk(relaxed = true) } @Test @@ -55,13 +55,13 @@ class InstallAppWorkerTest { val result = worker.doWork() assertThat(result).isEqualTo(androidx.work.ListenableWorker.Result.failure()) - coVerify(exactly = 0) { appInstallProcessor.processInstall(any(), any(), any()) } + coVerify(exactly = 0) { appInstallationFacade.processInstall(any(), any(), any()) } } @Test fun doWork_returnsSuccessWhenProcessorSucceeds() = runTest { coEvery { - appInstallProcessor.processInstall("123", true, any()) + appInstallationFacade.processInstall("123", true, any()) } returns Result.success(ResultStatus.OK) val worker = createWorker( Data.Builder() @@ -73,13 +73,13 @@ class InstallAppWorkerTest { val result = worker.doWork() assertThat(result).isEqualTo(androidx.work.ListenableWorker.Result.success()) - coVerify { appInstallProcessor.processInstall("123", true, any()) } + coVerify { appInstallationFacade.processInstall("123", true, any()) } } @Test fun doWork_returnsFailureWhenProcessorFails() = runTest { coEvery { - appInstallProcessor.processInstall("123", false, any()) + appInstallationFacade.processInstall("123", false, any()) } returns Result.failure(IllegalStateException("boom")) val worker = createWorker( Data.Builder() @@ -91,7 +91,7 @@ class InstallAppWorkerTest { val result = worker.doWork() assertThat(result).isEqualTo(androidx.work.ListenableWorker.Result.failure()) - coVerify { appInstallProcessor.processInstall("123", false, any()) } + coVerify { appInstallationFacade.processInstall("123", false, any()) } } private fun createWorker(inputData: Data): InstallAppWorker { @@ -101,7 +101,7 @@ class InstallAppWorkerTest { return InstallAppWorker( ApplicationProvider.getApplicationContext(), params, - appInstallProcessor + appInstallationFacade ) } } -- GitLab From 16204b3bee12610590de6b7a6639c585e096a448 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Wed, 18 Mar 2026 21:07:14 +0600 Subject: [PATCH 25/43] refactor: rename AppInstallWorkRunner to InstallationProcessor --- .../apps/data/install/core/AppInstallationFacade.kt | 6 +++--- ...InstallWorkRunner.kt => InstallationProcessor.kt} | 2 +- .../installProcessor/AppInstallationFacadeTest.kt | 12 ++++++------ ...orkRunnerTest.kt => InstallationProcessorTest.kt} | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) rename app/src/main/java/foundation/e/apps/data/install/core/helper/{AppInstallWorkRunner.kt => InstallationProcessor.kt} (99%) rename app/src/test/java/foundation/e/apps/installProcessor/{AppInstallWorkRunnerTest.kt => InstallationProcessorTest.kt} (97%) diff --git a/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt b/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt index 141ea8a9a..b79f1d5f7 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt @@ -24,14 +24,14 @@ import foundation.e.apps.data.install.AppInstallComponents import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.core.helper.InstallationRequest import foundation.e.apps.data.install.core.helper.InstallationEnqueuer -import foundation.e.apps.data.install.core.helper.AppInstallWorkRunner +import foundation.e.apps.data.install.core.helper.InstallationProcessor import foundation.e.apps.domain.model.install.Status import javax.inject.Inject class AppInstallationFacade @Inject constructor( private val appInstallComponents: AppInstallComponents, private val installationEnqueuer: InstallationEnqueuer, - private val appInstallWorkRunner: AppInstallWorkRunner, + private val installationProcessor: InstallationProcessor, private val installationRequest: InstallationRequest, ) { /** @@ -73,6 +73,6 @@ class AppInstallationFacade @Inject constructor( isItUpdateWork: Boolean, runInForeground: (suspend (String) -> Unit) ): Result { - return appInstallWorkRunner.processInstall(fusedDownloadId, isItUpdateWork, runInForeground) + return installationProcessor.processInstall(fusedDownloadId, isItUpdateWork, runInForeground) } } diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallWorkRunner.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationProcessor.kt similarity index 99% rename from app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallWorkRunner.kt rename to app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationProcessor.kt index 3cb430e6f..3eec9ef8c 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppInstallWorkRunner.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationProcessor.kt @@ -30,7 +30,7 @@ import kotlinx.coroutines.flow.transformWhile import timber.log.Timber import javax.inject.Inject -class AppInstallWorkRunner @Inject constructor( +class InstallationProcessor @Inject constructor( private val appInstallRepository: AppInstallRepository, private val appManagerWrapper: AppManagerWrapper, private val downloadManager: DownloadManagerUtils, diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt index 51e15d619..ea54d5627 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt @@ -31,7 +31,7 @@ import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.core.AppInstallationFacade import foundation.e.apps.data.install.core.helper.InstallationRequest import foundation.e.apps.data.install.core.helper.InstallationEnqueuer -import foundation.e.apps.data.install.core.helper.AppInstallWorkRunner +import foundation.e.apps.data.install.core.helper.InstallationProcessor import foundation.e.apps.util.MainCoroutineRule import io.mockk.coEvery import io.mockk.coVerify @@ -57,7 +57,7 @@ class AppInstallationFacadeTest { private lateinit var appInstallationFacade: AppInstallationFacade private lateinit var installationRequest: InstallationRequest private lateinit var installationEnqueuer: InstallationEnqueuer - private lateinit var appInstallWorkRunner: AppInstallWorkRunner + private lateinit var installationProcessor: InstallationProcessor @Before fun setup() { @@ -66,12 +66,12 @@ class AppInstallationFacadeTest { val appInstallComponents = AppInstallComponents(appInstallRepository, appManagerWrapper) installationRequest = mockk(relaxed = true) installationEnqueuer = mockk(relaxed = true) - appInstallWorkRunner = mockk(relaxed = true) + installationProcessor = mockk(relaxed = true) appInstallationFacade = AppInstallationFacade( appInstallComponents, installationEnqueuer, - appInstallWorkRunner, + installationProcessor, installationRequest ) } @@ -118,7 +118,7 @@ class AppInstallationFacadeTest { @Test fun processInstall_delegatesResult() = runTest { coEvery { - appInstallWorkRunner.processInstall("123", false, any()) + installationProcessor.processInstall("123", false, any()) } returns Result.success(ResultStatus.OK) val result = appInstallationFacade.processInstall("123", false) { @@ -126,6 +126,6 @@ class AppInstallationFacadeTest { } assertEquals(ResultStatus.OK, result.getOrNull()) - coVerify { appInstallWorkRunner.processInstall("123", false, any()) } + coVerify { installationProcessor.processInstall("123", false, any()) } } } diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallWorkRunnerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt similarity index 97% rename from app/src/test/java/foundation/e/apps/installProcessor/AppInstallWorkRunnerTest.kt rename to app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt index fce9d6dc1..decdaa66a 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallWorkRunnerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt @@ -25,7 +25,7 @@ import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.download.DownloadManagerUtils import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.install.core.helper.AppInstallWorkRunner +import foundation.e.apps.data.install.core.helper.InstallationProcessor import foundation.e.apps.data.install.core.helper.AppUpdateCompletionHandler import foundation.e.apps.domain.model.install.Status import foundation.e.apps.util.MainCoroutineRule @@ -42,7 +42,7 @@ import org.mockito.Mock import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) -class AppInstallWorkRunnerTest { +class InstallationProcessorTest { @Rule @JvmField val instantExecutorRule = InstantTaskExecutorRule() @@ -55,7 +55,7 @@ class AppInstallWorkRunnerTest { private lateinit var fakeFusedManagerRepository: FakeAppManagerWrapper private lateinit var downloadManagerUtils: DownloadManagerUtils private lateinit var appUpdateCompletionHandler: AppUpdateCompletionHandler - private lateinit var workRunner: AppInstallWorkRunner + private lateinit var workRunner: InstallationProcessor private lateinit var context: Context @Mock @@ -74,7 +74,7 @@ class AppInstallWorkRunnerTest { FakeAppManagerWrapper(fakeFusedDownloadDAO, context, fakeFusedManager, fakeFDroidRepository) downloadManagerUtils = mockk(relaxed = true) appUpdateCompletionHandler = mockk(relaxed = true) - workRunner = AppInstallWorkRunner( + workRunner = InstallationProcessor( appInstallRepository, fakeFusedManagerRepository, downloadManagerUtils, -- GitLab From 395a7343195deed767b6cff3f9472de05ea4e17f Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Wed, 18 Mar 2026 21:10:22 +0600 Subject: [PATCH 26/43] refactor: rename AppUpdateCompletionHandler to InstallationCompletionHandler --- ...pletionHandler.kt => InstallationCompletionHandler.kt} | 2 +- .../data/install/core/helper/InstallationProcessor.kt | 4 ++-- ...andlerTest.kt => InstallationCompletionHandlerTest.kt} | 8 ++++---- .../e/apps/installProcessor/InstallationProcessorTest.kt | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) rename app/src/main/java/foundation/e/apps/data/install/core/helper/{AppUpdateCompletionHandler.kt => InstallationCompletionHandler.kt} (98%) rename app/src/test/java/foundation/e/apps/installProcessor/{AppUpdateCompletionHandlerTest.kt => InstallationCompletionHandlerTest.kt} (96%) diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppUpdateCompletionHandler.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationCompletionHandler.kt similarity index 98% rename from app/src/main/java/foundation/e/apps/data/install/core/helper/AppUpdateCompletionHandler.kt rename to app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationCompletionHandler.kt index 88a8ba843..0fec3c300 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/AppUpdateCompletionHandler.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationCompletionHandler.kt @@ -34,7 +34,7 @@ import java.util.Date import java.util.Locale import javax.inject.Inject -class AppUpdateCompletionHandler @Inject constructor( +class InstallationCompletionHandler @Inject constructor( @ApplicationContext private val context: Context, private val appInstallRepository: AppInstallRepository, private val appManagerWrapper: AppManagerWrapper, diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationProcessor.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationProcessor.kt index 3eec9ef8c..e25791fa2 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationProcessor.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationProcessor.kt @@ -34,7 +34,7 @@ class InstallationProcessor @Inject constructor( private val appInstallRepository: AppInstallRepository, private val appManagerWrapper: AppManagerWrapper, private val downloadManager: DownloadManagerUtils, - private val appUpdateCompletionHandler: AppUpdateCompletionHandler, + private val installationCompletionHandler: InstallationCompletionHandler, ) { @Suppress("ReturnCount") @OptIn(DelicateCoroutinesApi::class) @@ -185,6 +185,6 @@ class InstallationProcessor @Inject constructor( } private suspend fun finishInstallation(appInstall: AppInstall, isUpdateWork: Boolean) { - appUpdateCompletionHandler.onInstallFinished(appInstall, isUpdateWork) + installationCompletionHandler.onInstallFinished(appInstall, isUpdateWork) } } diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppUpdateCompletionHandlerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationCompletionHandlerTest.kt similarity index 96% rename from app/src/test/java/foundation/e/apps/installProcessor/AppUpdateCompletionHandlerTest.kt rename to app/src/test/java/foundation/e/apps/installProcessor/InstallationCompletionHandlerTest.kt index 6b8de8e47..43cc6a355 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppUpdateCompletionHandlerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationCompletionHandlerTest.kt @@ -24,7 +24,7 @@ import foundation.e.apps.R import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.install.core.helper.AppUpdateCompletionHandler +import foundation.e.apps.data.install.core.helper.InstallationCompletionHandler import foundation.e.apps.data.install.wrapper.UpdatesNotificationSender import foundation.e.apps.data.install.wrapper.UpdatesTracker import foundation.e.apps.data.preference.PlayStoreAuthStore @@ -42,7 +42,7 @@ import org.junit.Test import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) -class AppUpdateCompletionHandlerTest { +class InstallationCompletionHandlerTest { @get:Rule var mainCoroutineRule = MainCoroutineRule() @@ -53,7 +53,7 @@ class AppUpdateCompletionHandlerTest { private lateinit var updatesTracker: UpdatesTracker private lateinit var updatesNotificationSender: UpdatesNotificationSender private lateinit var context: Context - private lateinit var handler: AppUpdateCompletionHandler + private lateinit var handler: InstallationCompletionHandler @Before fun setup() { @@ -64,7 +64,7 @@ class AppUpdateCompletionHandlerTest { updatesTracker = mockk(relaxed = true) updatesNotificationSender = mockk(relaxed = true) coEvery { playStoreAuthStore.awaitAuthData() } returns null - handler = AppUpdateCompletionHandler( + handler = InstallationCompletionHandler( context, appInstallRepository, appManagerWrapper, diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt index decdaa66a..4bc469b63 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt @@ -26,7 +26,7 @@ import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.download.DownloadManagerUtils import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.core.helper.InstallationProcessor -import foundation.e.apps.data.install.core.helper.AppUpdateCompletionHandler +import foundation.e.apps.data.install.core.helper.InstallationCompletionHandler import foundation.e.apps.domain.model.install.Status import foundation.e.apps.util.MainCoroutineRule import io.mockk.mockk @@ -54,7 +54,7 @@ class InstallationProcessorTest { private lateinit var appInstallRepository: AppInstallRepository private lateinit var fakeFusedManagerRepository: FakeAppManagerWrapper private lateinit var downloadManagerUtils: DownloadManagerUtils - private lateinit var appUpdateCompletionHandler: AppUpdateCompletionHandler + private lateinit var installationCompletionHandler: InstallationCompletionHandler private lateinit var workRunner: InstallationProcessor private lateinit var context: Context @@ -73,12 +73,12 @@ class InstallationProcessorTest { fakeFusedManagerRepository = FakeAppManagerWrapper(fakeFusedDownloadDAO, context, fakeFusedManager, fakeFDroidRepository) downloadManagerUtils = mockk(relaxed = true) - appUpdateCompletionHandler = mockk(relaxed = true) + installationCompletionHandler = mockk(relaxed = true) workRunner = InstallationProcessor( appInstallRepository, fakeFusedManagerRepository, downloadManagerUtils, - appUpdateCompletionHandler + installationCompletionHandler ) } -- GitLab From 2a25cf5fe0f85d1e7a85ffa8838c4fb35c281287 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Wed, 18 Mar 2026 21:17:25 +0600 Subject: [PATCH 27/43] refactor: move core classes for app install into data/install/core package --- .../e/apps/data/install/core/AppInstallationFacade.kt | 7 ++----- .../data/install/core/{helper => }/InstallationEnqueuer.kt | 3 ++- .../install/core/{helper => }/InstallationProcessor.kt | 3 ++- .../data/install/core/{helper => }/InstallationRequest.kt | 2 +- .../java/foundation/e/apps/ui/MainActivityViewModel.kt | 2 +- .../e/apps/installProcessor/AppInstallationFacadeTest.kt | 6 +++--- .../e/apps/installProcessor/InstallationEnqueuerTest.kt | 2 +- .../e/apps/installProcessor/InstallationProcessorTest.kt | 2 +- .../e/apps/installProcessor/InstallationRequestTest.kt | 2 +- 9 files changed, 14 insertions(+), 15 deletions(-) rename app/src/main/java/foundation/e/apps/data/install/core/{helper => }/InstallationEnqueuer.kt (97%) rename app/src/main/java/foundation/e/apps/data/install/core/{helper => }/InstallationProcessor.kt (98%) rename app/src/main/java/foundation/e/apps/data/install/core/{helper => }/InstallationRequest.kt (97%) diff --git a/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt b/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt index b79f1d5f7..4d4d7cc01 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt @@ -22,9 +22,6 @@ import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.install.AppInstallComponents import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.install.core.helper.InstallationRequest -import foundation.e.apps.data.install.core.helper.InstallationEnqueuer -import foundation.e.apps.data.install.core.helper.InstallationProcessor import foundation.e.apps.domain.model.install.Status import javax.inject.Inject @@ -35,7 +32,7 @@ class AppInstallationFacade @Inject constructor( private val installationRequest: InstallationRequest, ) { /** - * creates [foundation.e.apps.data.install.models.AppInstall] from [foundation.e.apps.data.application.data.Application] and enqueues into WorkManager to run install process. + * creates [AppInstall] from [Application] and enqueues into WorkManager to run install process. * @param application represents the app info which will be installed * @param isAnUpdate indicates the app is requested for update or not * @@ -54,7 +51,7 @@ class AppInstallationFacade @Inject constructor( } /** - * Enqueues [foundation.e.apps.data.install.models.AppInstall] into WorkManager to run app install process. Before enqueuing, + * Enqueues [AppInstall] into WorkManager to run app install process. Before enqueuing, * It validates some corner cases * @param appInstall represents the app downloading and installing related info, example- Installing Status, * Url of the APK,OBB files are needed to be downloaded and installed etc. diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationEnqueuer.kt b/app/src/main/java/foundation/e/apps/data/install/core/InstallationEnqueuer.kt similarity index 97% rename from app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationEnqueuer.kt rename to app/src/main/java/foundation/e/apps/data/install/core/InstallationEnqueuer.kt index c5d2340d9..a86aa3697 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationEnqueuer.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/InstallationEnqueuer.kt @@ -16,13 +16,14 @@ * */ -package foundation.e.apps.data.install.core.helper +package foundation.e.apps.data.install.core import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppManagerWrapper +import foundation.e.apps.data.install.core.helper.PreEnqueueChecker import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.workmanager.InstallWorkManager import foundation.e.apps.data.install.wrapper.AppEventDispatcher diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationProcessor.kt b/app/src/main/java/foundation/e/apps/data/install/core/InstallationProcessor.kt similarity index 98% rename from app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationProcessor.kt rename to app/src/main/java/foundation/e/apps/data/install/core/InstallationProcessor.kt index e25791fa2..22c2174cb 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationProcessor.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/InstallationProcessor.kt @@ -16,11 +16,12 @@ * */ -package foundation.e.apps.data.install.core.helper +package foundation.e.apps.data.install.core import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.install.AppInstallRepository 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.models.AppInstall import foundation.e.apps.domain.model.install.Status diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationRequest.kt b/app/src/main/java/foundation/e/apps/data/install/core/InstallationRequest.kt similarity index 97% rename from app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationRequest.kt rename to app/src/main/java/foundation/e/apps/data/install/core/InstallationRequest.kt index 723aef3ff..eb351f0b1 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationRequest.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/InstallationRequest.kt @@ -16,7 +16,7 @@ * */ -package foundation.e.apps.data.install.core.helper +package foundation.e.apps.data.install.core import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.Source diff --git a/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt b/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt index e596bae11..151a18dd3 100644 --- a/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt @@ -33,10 +33,10 @@ import foundation.e.apps.data.application.mapper.toApplication import foundation.e.apps.data.enums.isInitialized import foundation.e.apps.data.enums.isUnFiltered import foundation.e.apps.data.install.AppManagerWrapper +import foundation.e.apps.data.install.core.AppInstallationFacade import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PwaManager -import foundation.e.apps.data.install.core.AppInstallationFacade import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.system.NetworkStatusManager import foundation.e.apps.domain.application.ApplicationDomain diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt index ea54d5627..747bde6e2 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt @@ -29,9 +29,9 @@ import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.core.AppInstallationFacade -import foundation.e.apps.data.install.core.helper.InstallationRequest -import foundation.e.apps.data.install.core.helper.InstallationEnqueuer -import foundation.e.apps.data.install.core.helper.InstallationProcessor +import foundation.e.apps.data.install.core.InstallationRequest +import foundation.e.apps.data.install.core.InstallationEnqueuer +import foundation.e.apps.data.install.core.InstallationProcessor import foundation.e.apps.util.MainCoroutineRule import io.mockk.coEvery import io.mockk.coVerify diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt index 7439266b6..4e5ba464a 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt @@ -35,7 +35,7 @@ import foundation.e.apps.data.install.core.helper.AgeLimitGate import foundation.e.apps.data.install.core.helper.DevicePreconditions import foundation.e.apps.data.install.core.helper.DownloadUrlRefresher import foundation.e.apps.data.install.core.helper.PreEnqueueChecker -import foundation.e.apps.data.install.core.helper.InstallationEnqueuer +import foundation.e.apps.data.install.core.InstallationEnqueuer import foundation.e.apps.data.install.workmanager.InstallWorkManager import foundation.e.apps.data.install.wrapper.NetworkStatusChecker import foundation.e.apps.data.install.wrapper.StorageSpaceChecker diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt index 4bc469b63..8212c636e 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt @@ -25,7 +25,7 @@ import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.download.DownloadManagerUtils import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.install.core.helper.InstallationProcessor +import foundation.e.apps.data.install.core.InstallationProcessor import foundation.e.apps.data.install.core.helper.InstallationCompletionHandler import foundation.e.apps.domain.model.install.Status import foundation.e.apps.util.MainCoroutineRule diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallationRequestTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationRequestTest.kt index 5a0d37c3c..38495a31a 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/InstallationRequestTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationRequestTest.kt @@ -22,7 +22,7 @@ import com.aurora.gplayapi.data.models.ContentRating import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Type -import foundation.e.apps.data.install.core.helper.InstallationRequest +import foundation.e.apps.data.install.core.InstallationRequest import foundation.e.apps.domain.model.install.Status import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue -- GitLab From 2d37cd2f29300f3d011c31ab10416a6af7ecb18d Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Wed, 18 Mar 2026 21:27:07 +0600 Subject: [PATCH 28/43] refactor: rename AppInstallProcessorBindingsModule to AppInstallationModule --- ...stallProcessorBindingsModule.kt => AppInstallationModule.kt} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename app/src/main/java/foundation/e/apps/data/di/bindings/{AppInstallProcessorBindingsModule.kt => AppInstallationModule.kt} (98%) diff --git a/app/src/main/java/foundation/e/apps/data/di/bindings/AppInstallProcessorBindingsModule.kt b/app/src/main/java/foundation/e/apps/data/di/bindings/AppInstallationModule.kt similarity index 98% rename from app/src/main/java/foundation/e/apps/data/di/bindings/AppInstallProcessorBindingsModule.kt rename to app/src/main/java/foundation/e/apps/data/di/bindings/AppInstallationModule.kt index ed205c78b..d4e7cbf8a 100644 --- a/app/src/main/java/foundation/e/apps/data/di/bindings/AppInstallProcessorBindingsModule.kt +++ b/app/src/main/java/foundation/e/apps/data/di/bindings/AppInstallationModule.kt @@ -38,7 +38,7 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -interface AppInstallProcessorBindingsModule { +interface AppInstallationModule { @Binds @Singleton -- GitLab From f15a1cfa1c33be247841af8df60e09d0ea40d4c0 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Wed, 18 Mar 2026 21:32:43 +0600 Subject: [PATCH 29/43] refactor: update DownloadUrlRefresher to explicitly return false within the error handler when an AppNotPurchased exception occurs. Modify handleAppNotPurchased to return Unit instead of a hardcoded Boolean, moving the return logic to the call site for better clarity. --- .../apps/data/install/core/helper/DownloadUrlRefresher.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/DownloadUrlRefresher.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/DownloadUrlRefresher.kt index 10bcc0bb5..3b1bc916d 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/DownloadUrlRefresher.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/DownloadUrlRefresher.kt @@ -65,7 +65,10 @@ class DownloadUrlRefresher @Inject constructor( ): Boolean { return when (throwable) { is CancellationException -> throw throwable - is InternalException.AppNotPurchased -> handleAppNotPurchased(appInstall) + is InternalException.AppNotPurchased -> { + handleAppNotPurchased(appInstall) + false + } is Exception -> { val message = if (throwable is GplayHttpRequestException) { "${appInstall.packageName} code: ${throwable.status} exception: ${throwable.message}" @@ -81,7 +84,7 @@ class DownloadUrlRefresher @Inject constructor( } } - private suspend fun handleAppNotPurchased(appInstall: AppInstall): Boolean { + private suspend fun handleAppNotPurchased(appInstall: AppInstall) { if (appInstall.isFree) { appEventDispatcher.dispatch(AppEvent.AppRestrictedOrUnavailable(appInstall)) appManager.addDownload(appInstall) @@ -90,7 +93,6 @@ class DownloadUrlRefresher @Inject constructor( appManagerWrapper.addFusedDownloadPurchaseNeeded(appInstall) appEventDispatcher.dispatch(AppEvent.AppPurchaseEvent(appInstall)) } - return false } private suspend fun handleUpdateDownloadError(appInstall: AppInstall, isAnUpdate: Boolean) { -- GitLab From 7d00aa79adf5f15805a592c3e983c120f4ea26a1 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Thu, 26 Mar 2026 17:20:01 +0600 Subject: [PATCH 30/43] refactor: rename AgeLimitGate to AgeLimiter --- .../helper/{AgeLimitGate.kt => AgeLimiter.kt} | 2 +- .../install/core/helper/PreEnqueueChecker.kt | 4 ++-- .../{AgeLimitGateTest.kt => AgeLimiterTest.kt} | 8 ++++---- .../InstallationEnqueuerTest.kt | 18 +++++++++--------- .../installProcessor/PreEnqueueCheckerTest.kt | 18 +++++++++--------- 5 files changed, 25 insertions(+), 25 deletions(-) rename app/src/main/java/foundation/e/apps/data/install/core/helper/{AgeLimitGate.kt => AgeLimiter.kt} (98%) rename app/src/test/java/foundation/e/apps/installProcessor/{AgeLimitGateTest.kt => AgeLimiterTest.kt} (97%) diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/AgeLimitGate.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/AgeLimiter.kt similarity index 98% rename from app/src/main/java/foundation/e/apps/data/install/core/helper/AgeLimitGate.kt rename to app/src/main/java/foundation/e/apps/data/install/core/helper/AgeLimiter.kt index 2023908a8..c8f77bc81 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/AgeLimitGate.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/AgeLimiter.kt @@ -30,7 +30,7 @@ import foundation.e.apps.domain.model.ContentRatingValidity import kotlinx.coroutines.CompletableDeferred import javax.inject.Inject -class AgeLimitGate @Inject constructor( +class AgeLimiter @Inject constructor( private val validateAppAgeLimitUseCase: ValidateAppAgeLimitUseCase, private val appManagerWrapper: AppManagerWrapper, private val appEventDispatcher: AppEventDispatcher, diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/PreEnqueueChecker.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/PreEnqueueChecker.kt index 7d6cba8de..c85c82b30 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/PreEnqueueChecker.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/PreEnqueueChecker.kt @@ -27,7 +27,7 @@ import javax.inject.Inject class PreEnqueueChecker @Inject constructor( private val downloadUrlRefresher: DownloadUrlRefresher, private val appManagerWrapper: AppManagerWrapper, - private val ageLimitGate: AgeLimitGate, + private val ageLimiter: AgeLimiter, private val devicePreconditions: DevicePreconditions, ) { suspend fun canEnqueue(appInstall: AppInstall, isAnUpdate: Boolean = false): Boolean { @@ -35,7 +35,7 @@ class PreEnqueueChecker @Inject constructor( downloadUrlRefresher.updateDownloadUrls(appInstall, isAnUpdate) val isDownloadAdded = hasUpdatedDownloadUrls && addDownload(appInstall) - val isAgeLimitAllowed = isDownloadAdded && ageLimitGate.allow(appInstall) + val isAgeLimitAllowed = isDownloadAdded && ageLimiter.allow(appInstall) return isAgeLimitAllowed && devicePreconditions.canProceed(appInstall) } diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AgeLimitGateTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AgeLimiterTest.kt similarity index 97% rename from app/src/test/java/foundation/e/apps/installProcessor/AgeLimitGateTest.kt rename to app/src/test/java/foundation/e/apps/installProcessor/AgeLimiterTest.kt index 8eccf4606..8fd9fd55b 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AgeLimitGateTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AgeLimiterTest.kt @@ -24,7 +24,7 @@ import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.install.core.helper.AgeLimitGate +import foundation.e.apps.data.install.core.helper.AgeLimiter import foundation.e.apps.data.install.wrapper.ParentalControlAuthGateway import foundation.e.apps.domain.ValidateAppAgeLimitUseCase import foundation.e.apps.domain.model.ContentRatingValidity @@ -41,12 +41,12 @@ import org.junit.Test import org.mockito.Mockito @OptIn(ExperimentalCoroutinesApi::class) -class AgeLimitGateTest { +class AgeLimiterTest { private lateinit var validateAppAgeLimitUseCase: ValidateAppAgeLimitUseCase private lateinit var appManagerWrapper: AppManagerWrapper private lateinit var parentalControlAuthGateway: ParentalControlAuthGateway private lateinit var appEventDispatcher: FakeAppEventDispatcher - private lateinit var gate: AgeLimitGate + private lateinit var gate: AgeLimiter @Before fun setup() { @@ -54,7 +54,7 @@ class AgeLimitGateTest { appManagerWrapper = mockk(relaxed = true) parentalControlAuthGateway = mockk(relaxed = true) appEventDispatcher = FakeAppEventDispatcher(autoCompleteDeferred = true) - gate = AgeLimitGate( + gate = AgeLimiter( validateAppAgeLimitUseCase, appManagerWrapper, appEventDispatcher, diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt index 4e5ba464a..37e40e8a6 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt @@ -31,7 +31,7 @@ import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager -import foundation.e.apps.data.install.core.helper.AgeLimitGate +import foundation.e.apps.data.install.core.helper.AgeLimiter import foundation.e.apps.data.install.core.helper.DevicePreconditions import foundation.e.apps.data.install.core.helper.DownloadUrlRefresher import foundation.e.apps.data.install.core.helper.PreEnqueueChecker @@ -68,7 +68,7 @@ class InstallationEnqueuerTest { private lateinit var sessionRepository: SessionRepository private lateinit var playStoreAuthStore: PlayStoreAuthStore private lateinit var storageNotificationManager: StorageNotificationManager - private lateinit var ageLimitGate: AgeLimitGate + private lateinit var ageLimiter: AgeLimiter private lateinit var appEventDispatcher: FakeAppEventDispatcher private lateinit var storageSpaceChecker: StorageSpaceChecker private lateinit var networkStatusChecker: NetworkStatusChecker @@ -87,7 +87,7 @@ class InstallationEnqueuerTest { sessionRepository = mockk(relaxed = true) playStoreAuthStore = mockk(relaxed = true) storageNotificationManager = mockk(relaxed = true) - ageLimitGate = mockk(relaxed = true) + ageLimiter = mockk(relaxed = true) appEventDispatcher = FakeAppEventDispatcher() storageSpaceChecker = mockk(relaxed = true) networkStatusChecker = mockk(relaxed = true) @@ -111,7 +111,7 @@ class InstallationEnqueuerTest { preflightChecker = PreEnqueueChecker( downloadUrlRefresher, appManagerWrapper, - ageLimitGate, + ageLimiter, devicePreconditions ) enqueuer = InstallationEnqueuer( @@ -129,7 +129,7 @@ class InstallationEnqueuerTest { val appInstall = createPwaInstall() coEvery { appManagerWrapper.addDownload(appInstall) } returns true - coEvery { ageLimitGate.allow(appInstall) } returns true + coEvery { ageLimiter.allow(appInstall) } returns true every { networkStatusChecker.isNetworkAvailable() } returns true every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L @@ -143,7 +143,7 @@ class InstallationEnqueuerTest { val appInstall = createPwaInstall() coEvery { appManagerWrapper.addDownload(appInstall) } returns true - coEvery { ageLimitGate.allow(appInstall) } returns true + coEvery { ageLimiter.allow(appInstall) } returns true every { networkStatusChecker.isNetworkAvailable() } returns false every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L @@ -158,7 +158,7 @@ class InstallationEnqueuerTest { val appInstall = createPwaInstall() coEvery { appManagerWrapper.addDownload(appInstall) } returns true - coEvery { ageLimitGate.allow(appInstall) } returns true + coEvery { ageLimiter.allow(appInstall) } returns true every { networkStatusChecker.isNetworkAvailable() } returns true every { storageSpaceChecker.spaceMissing(appInstall) } returns 100L @@ -178,7 +178,7 @@ class InstallationEnqueuerTest { val result = enqueuer.canEnqueue(appInstall) assertFalse(result) - coVerify(exactly = 0) { ageLimitGate.allow(any()) } + coVerify(exactly = 0) { ageLimiter.allow(any()) } } @Test @@ -192,7 +192,7 @@ class InstallationEnqueuerTest { playStoreAuthStore.awaitAuthData() } returns AuthData(email = "anon@example.com", isAnonymous = true) coEvery { appManagerWrapper.addDownload(appInstall) } returns true - coEvery { ageLimitGate.allow(appInstall) } returns true + coEvery { ageLimiter.allow(appInstall) } returns true every { networkStatusChecker.isNetworkAvailable() } returns true every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L justRun { InstallWorkManager.enqueueWork(any(), any(), any()) } diff --git a/app/src/test/java/foundation/e/apps/installProcessor/PreEnqueueCheckerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/PreEnqueueCheckerTest.kt index bd46bdb07..e19498e77 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/PreEnqueueCheckerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/PreEnqueueCheckerTest.kt @@ -22,7 +22,7 @@ import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Type import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.install.core.helper.AgeLimitGate +import foundation.e.apps.data.install.core.helper.AgeLimiter import foundation.e.apps.data.install.core.helper.DevicePreconditions import foundation.e.apps.data.install.core.helper.DownloadUrlRefresher import foundation.e.apps.data.install.core.helper.PreEnqueueChecker @@ -41,7 +41,7 @@ import org.junit.Test class PreEnqueueCheckerTest { private lateinit var downloadUrlRefresher: DownloadUrlRefresher private lateinit var appManagerWrapper: AppManagerWrapper - private lateinit var ageLimitGate: AgeLimitGate + private lateinit var ageLimiter: AgeLimiter private lateinit var devicePreconditions: DevicePreconditions private lateinit var checker: PreEnqueueChecker @@ -49,12 +49,12 @@ class PreEnqueueCheckerTest { fun setup() { downloadUrlRefresher = mockk(relaxed = true) appManagerWrapper = mockk(relaxed = true) - ageLimitGate = mockk(relaxed = true) + ageLimiter = mockk(relaxed = true) devicePreconditions = mockk(relaxed = true) checker = PreEnqueueChecker( downloadUrlRefresher, appManagerWrapper, - ageLimitGate, + ageLimiter, devicePreconditions ) } @@ -63,7 +63,7 @@ class PreEnqueueCheckerTest { fun canEnqueue_skipsDownloadUrlRefreshForPwaInstalls() = runTest { val appInstall = createPwaInstall() coEvery { appManagerWrapper.addDownload(appInstall) } returns true - coEvery { ageLimitGate.allow(appInstall) } returns true + coEvery { ageLimiter.allow(appInstall) } returns true coEvery { devicePreconditions.canProceed(appInstall) } returns true val result = checker.canEnqueue(appInstall) @@ -83,7 +83,7 @@ class PreEnqueueCheckerTest { assertFalse(result) coVerify(exactly = 0) { appManagerWrapper.addDownload(any()) } - coVerify(exactly = 0) { ageLimitGate.allow(any()) } + coVerify(exactly = 0) { ageLimiter.allow(any()) } coVerify(exactly = 0) { devicePreconditions.canProceed(any()) } } @@ -96,7 +96,7 @@ class PreEnqueueCheckerTest { val result = checker.canEnqueue(appInstall) assertFalse(result) - coVerify(exactly = 0) { ageLimitGate.allow(any()) } + coVerify(exactly = 0) { ageLimiter.allow(any()) } coVerify(exactly = 0) { devicePreconditions.canProceed(any()) } } @@ -105,7 +105,7 @@ class PreEnqueueCheckerTest { val appInstall = createNativeInstall() coEvery { downloadUrlRefresher.updateDownloadUrls(appInstall, false) } returns true coEvery { appManagerWrapper.addDownload(appInstall) } returns true - coEvery { ageLimitGate.allow(appInstall) } returns false + coEvery { ageLimiter.allow(appInstall) } returns false val result = checker.canEnqueue(appInstall) @@ -118,7 +118,7 @@ class PreEnqueueCheckerTest { val appInstall = createNativeInstall() coEvery { downloadUrlRefresher.updateDownloadUrls(appInstall, false) } returns true coEvery { appManagerWrapper.addDownload(appInstall) } returns true - coEvery { ageLimitGate.allow(appInstall) } returns true + coEvery { ageLimiter.allow(appInstall) } returns true coEvery { devicePreconditions.canProceed(appInstall) } returns true val result = checker.canEnqueue(appInstall) -- GitLab From 96db4c89ec3a7d143476d324dd35776581306ef8 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Thu, 26 Mar 2026 17:48:10 +0600 Subject: [PATCH 31/43] refactor: remove unused AppInstallComponents class --- app/detekt-baseline.xml | 1 - .../apps/data/install/AppInstallComponents.kt | 31 ------------------- .../install/core/AppInstallationFacade.kt | 6 ++-- .../AppInstallationFacadeTest.kt | 12 +++---- 4 files changed, 7 insertions(+), 43 deletions(-) delete mode 100644 app/src/main/java/foundation/e/apps/data/install/AppInstallComponents.kt diff --git a/app/detekt-baseline.xml b/app/detekt-baseline.xml index 9b4ff20b2..9f5e411a6 100644 --- a/app/detekt-baseline.xml +++ b/app/detekt-baseline.xml @@ -16,7 +16,6 @@ LongParameterList:MainActivityViewModel.kt$MainActivityViewModel$( private val appLoungeDataStore: AppLoungeDataStore, private val applicationRepository: ApplicationRepository, private val appManagerWrapper: AppManagerWrapper, private val appLoungePackageManager: AppLoungePackageManager, private val pwaManager: PwaManager, private val blockedAppRepository: BlockedAppRepository, private val gPlayContentRatingRepository: GPlayContentRatingRepository, private val fDroidAntiFeatureRepository: FDroidAntiFeatureRepository, private val appInstallProcessor: AppInstallProcessor, private val systemAppsUpdatesRepository: SystemAppsUpdatesRepository, private val reportFaultyTokenUseCase: ReportFaultyTokenUseCase, ) LongParameterList:UpdatesManagerImpl.kt$UpdatesManagerImpl$( @ApplicationContext private val context: Context, private val appLoungePackageManager: AppLoungePackageManager, private val applicationRepository: ApplicationRepository, private val faultyAppRepository: FaultyAppRepository, private val appLoungePreference: AppLoungePreference, private val fDroidRepository: FDroidRepository, private val blockedAppRepository: BlockedAppRepository, private val systemAppsUpdatesRepository: SystemAppsUpdatesRepository, ) LongParameterList:UpdatesWorker.kt$UpdatesWorker$( @Assisted private val context: Context, @Assisted private val params: WorkerParameters, private val updatesManagerRepository: UpdatesManagerRepository, private val appLoungeDataStore: AppLoungeDataStore, private val authenticatorRepository: AuthenticatorRepository, private val appInstallProcessor: AppInstallProcessor, private val blockedAppRepository: BlockedAppRepository, private val systemAppsUpdatesRepository: SystemAppsUpdatesRepository, ) - NoUnusedImports:AppInstallComponents.kt$foundation.e.apps.data.install.AppInstallComponents.kt ProtectedMemberInFinalClass:ApplicationListFragment.kt$ApplicationListFragment$// protected to avoid SyntheticAccessor protected val args: ApplicationListFragmentArgs by navArgs() ProtectedMemberInFinalClass:ApplicationListFragment.kt$ApplicationListFragment$// protected to avoid SyntheticAccessor protected val viewModel: ApplicationListViewModel by viewModels() ProtectedMemberInFinalClass:GoogleSignInFragment.kt$GoogleSignInFragment$// protected to avoid SyntheticAccessor protected val viewModel: LoginViewModel by lazy { ViewModelProvider(requireActivity())[LoginViewModel::class.java] } diff --git a/app/src/main/java/foundation/e/apps/data/install/AppInstallComponents.kt b/app/src/main/java/foundation/e/apps/data/install/AppInstallComponents.kt deleted file mode 100644 index 4133ec459..000000000 --- a/app/src/main/java/foundation/e/apps/data/install/AppInstallComponents.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.data.install - -import foundation.e.apps.data.install.AppInstallRepository -import foundation.e.apps.data.install.AppManagerWrapper -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class AppInstallComponents @Inject constructor( - val appInstallRepository: AppInstallRepository, - val appManagerWrapper: AppManagerWrapper -) diff --git a/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt b/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt index 4d4d7cc01..7488033dd 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt @@ -20,13 +20,13 @@ package foundation.e.apps.data.install.core import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.ResultStatus -import foundation.e.apps.data.install.AppInstallComponents +import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.domain.model.install.Status import javax.inject.Inject class AppInstallationFacade @Inject constructor( - private val appInstallComponents: AppInstallComponents, + private val appManagerWrapper: AppManagerWrapper, private val installationEnqueuer: InstallationEnqueuer, private val installationProcessor: InstallationProcessor, private val installationRequest: InstallationRequest, @@ -45,7 +45,7 @@ class AppInstallationFacade @Inject constructor( val isUpdate = isAnUpdate || application.status == Status.UPDATABLE || - appInstallComponents.appManagerWrapper.isFusedDownloadInstalled(appInstall) + appManagerWrapper.isFusedDownloadInstalled(appInstall) return enqueueFusedDownload(appInstall, isUpdate, application.isSystemApp) } diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt index 747bde6e2..9c85d976c 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt @@ -23,15 +23,13 @@ import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Type -import foundation.e.apps.domain.model.install.Status -import foundation.e.apps.data.install.AppInstallComponents -import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.core.AppInstallationFacade -import foundation.e.apps.data.install.core.InstallationRequest import foundation.e.apps.data.install.core.InstallationEnqueuer import foundation.e.apps.data.install.core.InstallationProcessor +import foundation.e.apps.data.install.core.InstallationRequest +import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.domain.model.install.Status import foundation.e.apps.util.MainCoroutineRule import io.mockk.coEvery import io.mockk.coVerify @@ -62,14 +60,12 @@ class AppInstallationFacadeTest { @Before fun setup() { appManagerWrapper = mockk(relaxed = true) - val appInstallRepository = mockk(relaxed = true) - val appInstallComponents = AppInstallComponents(appInstallRepository, appManagerWrapper) installationRequest = mockk(relaxed = true) installationEnqueuer = mockk(relaxed = true) installationProcessor = mockk(relaxed = true) appInstallationFacade = AppInstallationFacade( - appInstallComponents, + appManagerWrapper, installationEnqueuer, installationProcessor, installationRequest -- GitLab From 1ce14b4596020f9d0674ec15ad11f2bea60cc92a Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Fri, 27 Mar 2026 12:46:33 +0600 Subject: [PATCH 32/43] test: add more test coverage for InstallationEnqueuer --- .../InstallationEnqueuerTest.kt | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt index 37e40e8a6..c3bf7e140 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt @@ -19,8 +19,10 @@ package foundation.e.apps.installProcessor import android.content.Context +import androidx.work.Operation import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.exceptions.InternalException +import com.google.common.util.concurrent.Futures import foundation.e.apps.R import foundation.e.apps.data.application.ApplicationRepository import foundation.e.apps.data.enums.Source @@ -52,12 +54,14 @@ import io.mockk.mockk import io.mockk.mockkObject import io.mockk.unmockkObject import io.mockk.verify +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test +import kotlin.test.assertFailsWith @OptIn(ExperimentalCoroutinesApi::class) class InstallationEnqueuerTest { @@ -210,6 +214,100 @@ class InstallationEnqueuerTest { } } + @Test + fun enqueue_returnsFalseWhenCanEnqueueFails() = runTest { + val appInstall = createPwaInstall() + + mockkObject(InstallWorkManager) + try { + coEvery { appManagerWrapper.addDownload(appInstall) } returns false + + val result = enqueuer.enqueue(appInstall, isAnUpdate = true) + + assertFalse(result) + coVerify(exactly = 0) { appManagerWrapper.updateAwaiting(appInstall) } + verify(exactly = 0) { InstallWorkManager.enqueueWork(any(), any(), any()) } + } finally { + unmockkObject(InstallWorkManager) + } + } + + @Test + fun enqueue_enqueuesUpdateWorkWhenUpdateAndChecksPass() = runTest { + val appInstall = createPwaInstall() + + mockkObject(InstallWorkManager) + try { + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + coEvery { ageLimiter.allow(appInstall) } returns true + every { networkStatusChecker.isNetworkAvailable() } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + every { InstallWorkManager.getUniqueWorkName(appInstall.packageName) } answers { callOriginal() } + every { InstallWorkManager.enqueueWork(context, appInstall, true) } returns successfulOperation() + + val result = enqueuer.enqueue(appInstall, isAnUpdate = true) + + assertTrue(result) + coVerify { appManagerWrapper.updateAwaiting(appInstall) } + verify(exactly = 1) { InstallWorkManager.enqueueWork(context, appInstall, true) } + } finally { + unmockkObject(InstallWorkManager) + } + } + + @Test + fun enqueue_handlesGenericExceptionAndMarksInstallationIssue() = runTest { + val appInstall = createPwaInstall() + + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + coEvery { ageLimiter.allow(appInstall) } returns true + every { networkStatusChecker.isNetworkAvailable() } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + coEvery { appManagerWrapper.updateAwaiting(appInstall) } throws IllegalStateException("boom") + + val result = enqueuer.enqueue(appInstall) + + assertFalse(result) + coVerify { appManagerWrapper.installationIssue(appInstall) } + } + + @Test + fun enqueue_rethrowsCancellationException() = runTest { + val appInstall = createPwaInstall() + + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + coEvery { ageLimiter.allow(appInstall) } returns true + every { networkStatusChecker.isNetworkAvailable() } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + coEvery { appManagerWrapper.updateAwaiting(appInstall) } throws CancellationException("cancelled") + + assertFailsWith { + enqueuer.enqueue(appInstall) + } + } + + @Test + fun enqueue_doesNotWarnAnonymousPaidUsersForSystemApps() = runTest { + val appInstall = createPwaInstall(isFree = false) + + coEvery { sessionRepository.awaitUser() } returns User.ANONYMOUS + coEvery { + playStoreAuthStore.awaitAuthData() + } returns AuthData(email = "anon@example.com", isAnonymous = true) + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + coEvery { ageLimiter.allow(appInstall) } returns true + every { networkStatusChecker.isNetworkAvailable() } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + + val result = enqueuer.enqueue(appInstall, isSystemApp = true) + + assertTrue(result) + assertTrue(appEventDispatcher.events.none { + it is AppEvent.ErrorMessageEvent && it.data == R.string.paid_app_anonymous_message + }) + coVerify(exactly = 0) { playStoreAuthStore.awaitAuthData() } + } + @Test fun canEnqueue_handlesFreeAppNotPurchasedAsRestricted() = runTest { val appInstall = createNativeInstall(isFree = true) @@ -309,6 +407,12 @@ class InstallationEnqueuerTest { coVerify(exactly = 0) { appManagerWrapper.addDownload(appInstall) } } + private fun successfulOperation(): Operation { + val operation = mockk() + every { operation.result } returns Futures.immediateFuture(Operation.SUCCESS) + return operation + } + private fun createPwaInstall(isFree: Boolean = true) = AppInstall( type = Type.PWA, id = "123", -- GitLab From e0df29374d4fcd484182008ecacb61ebb197b48a Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Mon, 30 Mar 2026 12:00:25 +0600 Subject: [PATCH 33/43] refactor: improve InstallationEnqueuer class's readability --- .../data/install/core/InstallationEnqueuer.kt | 63 ++++++++++++------- .../InstallationEnqueuerTest.kt | 14 ++--- 2 files changed, 47 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/data/install/core/InstallationEnqueuer.kt b/app/src/main/java/foundation/e/apps/data/install/core/InstallationEnqueuer.kt index a86aa3697..ee69f6c77 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/InstallationEnqueuer.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/InstallationEnqueuer.kt @@ -48,23 +48,27 @@ class InstallationEnqueuer @Inject constructor( isSystemApp: Boolean = false ): Boolean { return runCatching { - dispatchAnonymousPaidAppWarning(appInstall, isSystemApp) + when { + isEnqueueingPaidAppInAnonymouseMode(appInstall, isSystemApp) -> { + dispatchAnonymousPaidAppWarning() + false + } - val canEnqueue = canEnqueue(appInstall, isAnUpdate) - if (canEnqueue) { - appManagerWrapper.updateAwaiting(appInstall) + canEnqueue(appInstall, isAnUpdate) -> { + appManagerWrapper.updateAwaiting(appInstall) + // Enqueueing installation work is managed by InstallOrchestrator#observeDownloads(). + // This method only handles update work. + if (isAnUpdate) { + enqueueUpdate(appInstall) + } + true + } - // Use only for update work for now. For installation work, see InstallOrchestrator#observeDownloads() - if (isAnUpdate) { - val uniqueWorkName = InstallWorkManager.getUniqueWorkName(appInstall.packageName) - InstallWorkManager.enqueueWork(context, appInstall, true) - Timber.d("UPDATE: Successfully enqueued unique work: $uniqueWorkName") + else -> { + Timber.w("Can't enqueue ${appInstall.name}/${appInstall.packageName} for installation/update.") + false } - } else { - Timber.w("Can't enqueue ${appInstall.name}/${appInstall.packageName} for installation.") } - - canEnqueue }.getOrElse { throwable -> when (throwable) { is CancellationException -> throw throwable @@ -82,19 +86,32 @@ class InstallationEnqueuer @Inject constructor( } } - suspend fun canEnqueue(appInstall: AppInstall, isAnUpdate: Boolean = false): Boolean { - return preEnqueueChecker.canEnqueue(appInstall, isAnUpdate) - } - - private suspend fun dispatchAnonymousPaidAppWarning(appInstall: AppInstall, isSystemApp: Boolean) { + private suspend fun isEnqueueingPaidAppInAnonymouseMode( + appInstall: AppInstall, + isSystemApp: Boolean + ): Boolean { val user = sessionRepository.awaitUser() if (!isSystemApp && (user == User.GOOGLE || user == User.ANONYMOUS)) { val authData = playStoreAuthStore.awaitAuthData() - if (!appInstall.isFree && authData?.isAnonymous == true) { - appEventDispatcher.dispatch( - AppEvent.ErrorMessageEvent(R.string.paid_app_anonymous_message) - ) - } + return !appInstall.isFree && authData?.isAnonymous == true + } else { + return false } } + + private fun enqueueUpdate(appInstall: AppInstall) { + val uniqueWorkName = InstallWorkManager.getUniqueWorkName(appInstall.packageName) + InstallWorkManager.enqueueWork(context, appInstall, true) + Timber.d("UPDATE: Successfully enqueued unique work: $uniqueWorkName") + } + + suspend fun canEnqueue(appInstall: AppInstall, isAnUpdate: Boolean = false): Boolean { + return preEnqueueChecker.canEnqueue(appInstall, isAnUpdate) + } + + private suspend fun dispatchAnonymousPaidAppWarning() { + appEventDispatcher.dispatch( + AppEvent.ErrorMessageEvent(R.string.paid_app_anonymous_message) + ) + } } diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt index c3bf7e140..c29cfaefc 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt @@ -79,7 +79,7 @@ class InstallationEnqueuerTest { private lateinit var appManager: AppManager private lateinit var devicePreconditions: DevicePreconditions private lateinit var downloadUrlRefresher: DownloadUrlRefresher - private lateinit var preflightChecker: PreEnqueueChecker + private lateinit var preEnqueueChecker: PreEnqueueChecker private lateinit var enqueuer: InstallationEnqueuer @Before @@ -112,7 +112,7 @@ class InstallationEnqueuerTest { storageSpaceChecker, networkStatusChecker ) - preflightChecker = PreEnqueueChecker( + preEnqueueChecker = PreEnqueueChecker( downloadUrlRefresher, appManagerWrapper, ageLimiter, @@ -120,7 +120,7 @@ class InstallationEnqueuerTest { ) enqueuer = InstallationEnqueuer( context, - preflightChecker, + preEnqueueChecker, appManagerWrapper, sessionRepository, playStoreAuthStore, @@ -186,8 +186,8 @@ class InstallationEnqueuerTest { } @Test - fun enqueue_warnsAnonymousPaidUsersWithoutAborting() = runTest { - val appInstall = createPwaInstall(isFree = false) + fun enqueue_warnsAnonymousPaidUsersAndAborts() = runTest { + val appInstall = createNativeInstall(isFree = false) mockkObject(InstallWorkManager) try { @@ -203,11 +203,11 @@ class InstallationEnqueuerTest { val result = enqueuer.enqueue(appInstall) - assertTrue(result) + assertFalse(result) assertTrue(appEventDispatcher.events.any { it is AppEvent.ErrorMessageEvent && it.data == R.string.paid_app_anonymous_message }) - coVerify { appManagerWrapper.updateAwaiting(appInstall) } + coVerify(exactly = 0) { appManagerWrapper.updateAwaiting(appInstall) } verify(exactly = 0) { InstallWorkManager.enqueueWork(any(), any(), any()) } } finally { unmockkObject(InstallWorkManager) -- GitLab From d7ed8b2a7d921c9c0ec6a874b8aafe7ba3c7f629 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Mon, 30 Mar 2026 12:56:01 +0600 Subject: [PATCH 34/43] refactor: simplify InstallationEnqueuer and add more test coverage --- .../data/install/core/InstallationEnqueuer.kt | 13 ++-- .../InstallationEnqueuerTest.kt | 63 +++++++++++++++++++ 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/data/install/core/InstallationEnqueuer.kt b/app/src/main/java/foundation/e/apps/data/install/core/InstallationEnqueuer.kt index ee69f6c77..9d179f8ec 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/InstallationEnqueuer.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/InstallationEnqueuer.kt @@ -91,11 +91,14 @@ class InstallationEnqueuer @Inject constructor( isSystemApp: Boolean ): Boolean { val user = sessionRepository.awaitUser() - if (!isSystemApp && (user == User.GOOGLE || user == User.ANONYMOUS)) { - val authData = playStoreAuthStore.awaitAuthData() - return !appInstall.isFree && authData?.isAnonymous == true - } else { - return false + return when { + isSystemApp -> false + user == User.ANONYMOUS -> { + val authData = playStoreAuthStore.awaitAuthData() + !appInstall.isFree && authData?.isAnonymous == true + } + + else -> false } } diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt index c29cfaefc..c4c4cb82f 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt @@ -308,6 +308,69 @@ class InstallationEnqueuerTest { coVerify(exactly = 0) { playStoreAuthStore.awaitAuthData() } } + @Test + fun enqueue_allowsPaidAppForGoogleUser() = runTest { + val appInstall = createNativeInstall(isFree = false) + + coEvery { sessionRepository.awaitUser() } returns User.GOOGLE + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + coEvery { ageLimiter.allow(appInstall) } returns true + every { networkStatusChecker.isNetworkAvailable() } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + + val result = enqueuer.enqueue(appInstall) + + assertTrue(result) + assertTrue(appEventDispatcher.events.none { + it is AppEvent.ErrorMessageEvent && it.data == R.string.paid_app_anonymous_message + }) + coVerify { appManagerWrapper.updateAwaiting(appInstall) } + coVerify(exactly = 0) { playStoreAuthStore.awaitAuthData() } + } + + @Test + fun enqueue_allowsFreeAppForAnonymousUser() = runTest { + val appInstall = createNativeInstall() + + coEvery { sessionRepository.awaitUser() } returns User.ANONYMOUS + coEvery { + playStoreAuthStore.awaitAuthData() + } returns AuthData(email = "anon@example.com", isAnonymous = true) + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + coEvery { ageLimiter.allow(appInstall) } returns true + every { networkStatusChecker.isNetworkAvailable() } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + + val result = enqueuer.enqueue(appInstall) + + assertTrue(result) + assertTrue(appEventDispatcher.events.none { + it is AppEvent.ErrorMessageEvent && it.data == R.string.paid_app_anonymous_message + }) + coVerify { appManagerWrapper.updateAwaiting(appInstall) } + coVerify { playStoreAuthStore.awaitAuthData() } + } + + @Test + fun enqueue_doesNotLookupAuthForNoGoogleUser() = runTest { + val appInstall = createNativeInstall() + + coEvery { sessionRepository.awaitUser() } returns User.NO_GOOGLE + coEvery { appManagerWrapper.addDownload(appInstall) } returns true + coEvery { ageLimiter.allow(appInstall) } returns true + every { networkStatusChecker.isNetworkAvailable() } returns true + every { storageSpaceChecker.spaceMissing(appInstall) } returns 0L + + val result = enqueuer.enqueue(appInstall) + + assertTrue(result) + assertTrue(appEventDispatcher.events.none { + it is AppEvent.ErrorMessageEvent && it.data == R.string.paid_app_anonymous_message + }) + coVerify { appManagerWrapper.updateAwaiting(appInstall) } + coVerify(exactly = 0) { playStoreAuthStore.awaitAuthData() } + } + @Test fun canEnqueue_handlesFreeAppNotPurchasedAsRestricted() = runTest { val appInstall = createNativeInstall(isFree = true) -- GitLab From 2b5c120e8d87226f760c3df7e42b0b605f69a6db Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Mon, 30 Mar 2026 15:02:37 +0600 Subject: [PATCH 35/43] build(data): prepare installation persistence support --- data/build.gradle | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/data/build.gradle b/data/build.gradle index ddb16e707..6699f24c0 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -1,6 +1,8 @@ plugins { id 'com.android.library' id 'kotlin-android' + id 'com.google.devtools.ksp' + id 'com.google.dagger.hilt.android' id 'jacoco' } @@ -51,7 +53,15 @@ dependencies { implementation libs.kotlinx.coroutines.core implementation libs.kotlinx.serialization.json implementation libs.gplayapi + implementation libs.gson implementation libs.hilt.android + implementation libs.lifecycle.livedata.ktx + implementation libs.room.ktx + implementation libs.room.runtime + + ksp libs.room.compiler + ksp libs.hilt.compile + ksp libs.hilt.compiler testImplementation libs.junit testImplementation libs.truth -- GitLab From 34deb653b6b86a877e7d2ca66ee512009b1fedf5 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Mon, 30 Mar 2026 15:13:52 +0600 Subject: [PATCH 36/43] refactor(install): decouple AppInstall from app-only helpers --- .../data/install/AppInstallIconUrlBuilder.kt | 15 ++++++++ .../e/apps/data/install/AppInstallMappings.kt | 38 +++++++++++++++++++ .../e/apps/data/install/AppManagerImpl.kt | 8 ++-- .../data/install/core/InstallationRequest.kt | 10 +++-- .../core/helper/DownloadUrlRefresher.kt | 3 +- .../install/core/helper/PreEnqueueChecker.kt | 4 +- .../install/download/DownloadManagerUtils.kt | 6 ++- .../e/apps/data/install/models/AppInstall.kt | 16 ++------ .../install/pkg/AppLoungePackageManager.kt | 8 ++-- .../e/apps/data/install/pkg/PwaManager.kt | 6 ++- .../e/apps/data/provider/AgeRatingProvider.kt | 5 ++- .../apps/domain/ValidateAppAgeLimitUseCase.kt | 14 +++---- .../e/apps/ui/MainActivityViewModel.kt | 5 ++- .../data/install/models/AppInstallTest.kt | 20 +++++----- .../e/apps/data/install/pkg/PwaManagerTest.kt | 5 ++- .../domain/ValidateAppAgeLimitUseCaseTest.kt | 8 ++-- .../DownloadUrlRefresherTest.kt | 7 ++-- .../InstallationEnqueuerTest.kt | 9 +++-- .../InstallationRequestTest.kt | 6 ++- .../installProcessor/PreEnqueueCheckerTest.kt | 10 ++--- .../installation/model/InstallationSource.kt | 8 ++++ .../installation/model/InstallationType.kt | 6 +++ 22 files changed, 148 insertions(+), 69 deletions(-) create mode 100644 app/src/main/java/foundation/e/apps/data/install/AppInstallIconUrlBuilder.kt create mode 100644 app/src/main/java/foundation/e/apps/data/install/AppInstallMappings.kt create mode 100644 data/src/main/java/foundation/e/apps/data/installation/model/InstallationSource.kt create mode 100644 data/src/main/java/foundation/e/apps/data/installation/model/InstallationType.kt diff --git a/app/src/main/java/foundation/e/apps/data/install/AppInstallIconUrlBuilder.kt b/app/src/main/java/foundation/e/apps/data/install/AppInstallIconUrlBuilder.kt new file mode 100644 index 000000000..fe2693261 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/install/AppInstallIconUrlBuilder.kt @@ -0,0 +1,15 @@ +package foundation.e.apps.data.install + +import foundation.e.apps.data.cleanapk.CleanApkRetrofit +import foundation.e.apps.data.installation.model.InstallationSource +import javax.inject.Inject + +class AppInstallIconUrlBuilder @Inject constructor() { + fun build(source: InstallationSource, iconImageUrl: String): String { + return if (source == InstallationSource.PLAY_STORE || source == InstallationSource.PWA) { + "${CleanApkRetrofit.ASSET_URL}$iconImageUrl" + } else { + iconImageUrl + } + } +} diff --git a/app/src/main/java/foundation/e/apps/data/install/AppInstallMappings.kt b/app/src/main/java/foundation/e/apps/data/install/AppInstallMappings.kt new file mode 100644 index 000000000..bbe5af1b6 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/install/AppInstallMappings.kt @@ -0,0 +1,38 @@ +package foundation.e.apps.data.install + +import foundation.e.apps.data.enums.Source +import foundation.e.apps.data.enums.Type +import foundation.e.apps.data.installation.model.InstallationSource +import foundation.e.apps.data.installation.model.InstallationType + +fun Source.toInstallationSource(): InstallationSource { + return when (this) { + Source.OPEN_SOURCE -> InstallationSource.OPEN_SOURCE + Source.PWA -> InstallationSource.PWA + Source.SYSTEM_APP -> InstallationSource.SYSTEM_APP + Source.PLAY_STORE -> InstallationSource.PLAY_STORE + } +} + +fun Type.toInstallationType(): InstallationType { + return when (this) { + Type.NATIVE -> InstallationType.NATIVE + Type.PWA -> InstallationType.PWA + } +} + +fun InstallationSource.toAppSource(): Source { + return when (this) { + InstallationSource.OPEN_SOURCE -> Source.OPEN_SOURCE + InstallationSource.PWA -> Source.PWA + InstallationSource.SYSTEM_APP -> Source.SYSTEM_APP + InstallationSource.PLAY_STORE -> Source.PLAY_STORE + } +} + +fun InstallationType.toAppType(): Type { + return when (this) { + InstallationType.NATIVE -> Type.NATIVE + InstallationType.PWA -> Type.PWA + } +} diff --git a/app/src/main/java/foundation/e/apps/data/install/AppManagerImpl.kt b/app/src/main/java/foundation/e/apps/data/install/AppManagerImpl.kt index b7509d197..3596d4ad2 100644 --- a/app/src/main/java/foundation/e/apps/data/install/AppManagerImpl.kt +++ b/app/src/main/java/foundation/e/apps/data/install/AppManagerImpl.kt @@ -28,11 +28,11 @@ import androidx.core.net.toUri import androidx.lifecycle.LiveData import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R -import foundation.e.apps.data.enums.Type import foundation.e.apps.data.install.download.data.DownloadProgressLD import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PwaManager +import foundation.e.apps.data.installation.model.InstallationType import foundation.e.apps.data.parentalcontrol.ContentRatingDao import foundation.e.apps.data.parentalcontrol.ContentRatingEntity import foundation.e.apps.domain.model.install.Status @@ -127,8 +127,8 @@ class AppManagerImpl @Inject constructor( override suspend fun downloadApp(appInstall: AppInstall, isUpdate: Boolean) { mutex.withLock { when (appInstall.type) { - Type.NATIVE -> downloadNativeApp(appInstall, isUpdate) - Type.PWA -> pwaManager.installPWAApp(appInstall) + InstallationType.NATIVE -> downloadNativeApp(appInstall, isUpdate) + InstallationType.PWA -> pwaManager.installPWAApp(appInstall) } } } @@ -136,7 +136,7 @@ class AppManagerImpl @Inject constructor( override suspend fun installApp(appInstall: AppInstall) { val list = mutableListOf() when (appInstall.type) { - Type.NATIVE -> { + InstallationType.NATIVE -> { val parentPathFile = File("$cacheDir/${appInstall.packageName}") parentPathFile.listFiles()?.let { list.addAll(it) } list.sort() diff --git a/app/src/main/java/foundation/e/apps/data/install/core/InstallationRequest.kt b/app/src/main/java/foundation/e/apps/data/install/core/InstallationRequest.kt index eb351f0b1..84d3e4ef4 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/InstallationRequest.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/InstallationRequest.kt @@ -20,22 +20,24 @@ package foundation.e.apps.data.install.core import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.enums.Type import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.install.toInstallationSource +import foundation.e.apps.data.install.toInstallationType +import foundation.e.apps.data.installation.model.InstallationType import javax.inject.Inject class InstallationRequest @Inject constructor() { fun create(application: Application): AppInstall { val appInstall = AppInstall( application._id, - application.source, + application.source.toInstallationSource(), application.status, application.name, application.package_name, mutableListOf(), mutableMapOf(), application.status, - application.type, + application.type.toInstallationType(), application.icon_image_path, application.latest_version_code, application.offer_type, @@ -45,7 +47,7 @@ class InstallationRequest @Inject constructor() { it.contentRating = application.contentRating } - if (appInstall.type == Type.PWA || application.source == Source.SYSTEM_APP) { + if (appInstall.type == InstallationType.PWA || application.source == Source.SYSTEM_APP) { appInstall.downloadURLList = mutableListOf(application.url) } diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/DownloadUrlRefresher.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/DownloadUrlRefresher.kt index 3b1bc916d..868b56ba7 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/DownloadUrlRefresher.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/DownloadUrlRefresher.kt @@ -27,6 +27,7 @@ import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.install.toAppSource import foundation.e.apps.data.install.wrapper.AppEventDispatcher import foundation.e.apps.data.playstore.utils.GplayHttpRequestException import kotlinx.coroutines.CancellationException @@ -43,7 +44,7 @@ class DownloadUrlRefresher @Inject constructor( suspend fun updateDownloadUrls(appInstall: AppInstall, isAnUpdate: Boolean): Boolean { return runCatching { applicationRepository.updateFusedDownloadWithDownloadingInfo( - appInstall.source, + appInstall.source.toAppSource(), appInstall ) }.fold( diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/PreEnqueueChecker.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/PreEnqueueChecker.kt index c85c82b30..056e73aa6 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/PreEnqueueChecker.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/PreEnqueueChecker.kt @@ -18,9 +18,9 @@ package foundation.e.apps.data.install.core.helper -import foundation.e.apps.data.enums.Type import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.InstallationType import timber.log.Timber import javax.inject.Inject @@ -31,7 +31,7 @@ class PreEnqueueChecker @Inject constructor( private val devicePreconditions: DevicePreconditions, ) { suspend fun canEnqueue(appInstall: AppInstall, isAnUpdate: Boolean = false): Boolean { - val hasUpdatedDownloadUrls = appInstall.type == Type.PWA || + val hasUpdatedDownloadUrls = appInstall.type == InstallationType.PWA || downloadUrlRefresher.updateDownloadUrls(appInstall, isAnUpdate) val isDownloadAdded = hasUpdatedDownloadUrls && addDownload(appInstall) diff --git a/app/src/main/java/foundation/e/apps/data/install/download/DownloadManagerUtils.kt b/app/src/main/java/foundation/e/apps/data/install/download/DownloadManagerUtils.kt index 883f37b76..4446a876e 100644 --- a/app/src/main/java/foundation/e/apps/data/install/download/DownloadManagerUtils.kt +++ b/app/src/main/java/foundation/e/apps/data/install/download/DownloadManagerUtils.kt @@ -23,12 +23,12 @@ import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R import foundation.e.apps.data.DownloadManager import foundation.e.apps.data.di.qualifiers.IoCoroutineScope -import foundation.e.apps.data.enums.Source import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.event.EventBus import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager +import foundation.e.apps.data.installation.model.InstallationSource import foundation.e.apps.domain.model.install.Status import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi @@ -171,7 +171,9 @@ class DownloadManagerUtils @Inject constructor( } private suspend fun checkCleanApkSignatureOK(appInstall: AppInstall): Boolean { - val isNonOpenSource = appInstall.source != Source.PWA && appInstall.source != Source.OPEN_SOURCE + val isNonOpenSource = + appInstall.source != InstallationSource.PWA && + appInstall.source != InstallationSource.OPEN_SOURCE val isSigned = appManagerWrapper.isFDroidApplicationSigned(context, appInstall) if (isNonOpenSource || isSigned) { Timber.d("Apk signature is OK") diff --git a/app/src/main/java/foundation/e/apps/data/install/models/AppInstall.kt b/app/src/main/java/foundation/e/apps/data/install/models/AppInstall.kt index ab9534435..52b76c696 100644 --- a/app/src/main/java/foundation/e/apps/data/install/models/AppInstall.kt +++ b/app/src/main/java/foundation/e/apps/data/install/models/AppInstall.kt @@ -5,22 +5,21 @@ import androidx.room.Ignore import androidx.room.PrimaryKey import com.aurora.gplayapi.data.models.ContentRating import com.aurora.gplayapi.data.models.PlayFile -import foundation.e.apps.data.cleanapk.CleanApkRetrofit -import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.enums.Type +import foundation.e.apps.data.installation.model.InstallationSource +import foundation.e.apps.data.installation.model.InstallationType import foundation.e.apps.domain.model.install.Status @Entity(tableName = "FusedDownload") data class AppInstall( @PrimaryKey val id: String = String(), - val source: Source = Source.PLAY_STORE, + val source: InstallationSource = InstallationSource.PLAY_STORE, var status: Status = Status.UNAVAILABLE, val name: String = String(), val packageName: String = String(), var downloadURLList: MutableList = mutableListOf(), var downloadIdMap: MutableMap = mutableMapOf(), val orgStatus: Status = Status.UNAVAILABLE, - val type: Type = Type.NATIVE, + val type: InstallationType = InstallationType.NATIVE, val iconImageUrl: String = String(), val versionCode: Long = 1, val offerType: Int = -1, @@ -43,11 +42,4 @@ data class AppInstall( fun isAwaiting() = status == Status.AWAITING fun areFilesDownloaded() = downloadIdMap.isNotEmpty() && !downloadIdMap.values.contains(false) - - fun getAppIconUrl(): String { - if (this.source == Source.PLAY_STORE || this.source == Source.PWA) { - return "${CleanApkRetrofit.ASSET_URL}${this.iconImageUrl}" - } - return this.iconImageUrl - } } diff --git a/app/src/main/java/foundation/e/apps/data/install/pkg/AppLoungePackageManager.kt b/app/src/main/java/foundation/e/apps/data/install/pkg/AppLoungePackageManager.kt index b60a4ce15..43aa31de2 100644 --- a/app/src/main/java/foundation/e/apps/data/install/pkg/AppLoungePackageManager.kt +++ b/app/src/main/java/foundation/e/apps/data/install/pkg/AppLoungePackageManager.kt @@ -32,9 +32,9 @@ import android.os.Build import androidx.core.content.pm.PackageInfoCompat import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.OpenForTesting -import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.enums.Type import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.InstallationSource +import foundation.e.apps.data.installation.model.InstallationType import foundation.e.apps.domain.model.install.Status import kotlinx.coroutines.DelicateCoroutinesApi import timber.log.Timber @@ -121,8 +121,8 @@ class AppLoungePackageManager @Inject constructor( if (appInstall == null || appInstall.packageName.isBlank()) { return } - if (appInstall.source == Source.PLAY_STORE) { - if (appInstall.type == Type.NATIVE && isInstalled(FAKE_STORE_PACKAGE_NAME)) { + if (appInstall.source == InstallationSource.PLAY_STORE) { + if (appInstall.type == InstallationType.NATIVE && isInstalled(FAKE_STORE_PACKAGE_NAME)) { val targetPackage = appInstall.packageName try { packageManager.setInstallerPackageName(targetPackage, FAKE_STORE_PACKAGE_NAME) diff --git a/app/src/main/java/foundation/e/apps/data/install/pkg/PwaManager.kt b/app/src/main/java/foundation/e/apps/data/install/pkg/PwaManager.kt index 6da37d2d4..a522373f1 100644 --- a/app/src/main/java/foundation/e/apps/data/install/pkg/PwaManager.kt +++ b/app/src/main/java/foundation/e/apps/data/install/pkg/PwaManager.kt @@ -14,6 +14,7 @@ import androidx.core.net.toUri import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.OpenForTesting import foundation.e.apps.data.application.data.Application +import foundation.e.apps.data.install.AppInstallIconUrlBuilder import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.domain.model.install.Status @@ -30,6 +31,7 @@ import javax.inject.Singleton class PwaManager @Inject constructor( @ApplicationContext private val context: Context, private val appInstallRepository: AppInstallRepository, + private val appInstallIconUrlBuilder: AppInstallIconUrlBuilder, ) { companion object { @@ -147,7 +149,9 @@ class PwaManager @Inject constructor( appInstallRepository.updateDownload(appInstall) // Get bitmap and byteArray for icon - val iconBitmap = getIconImageBitmap(appInstall.getAppIconUrl()) + val iconBitmap = getIconImageBitmap( + appInstallIconUrlBuilder.build(appInstall.source, appInstall.iconImageUrl) + ) if (iconBitmap == null) { appInstall.status = Status.INSTALLATION_ISSUE diff --git a/app/src/main/java/foundation/e/apps/data/provider/AgeRatingProvider.kt b/app/src/main/java/foundation/e/apps/data/provider/AgeRatingProvider.kt index 2c62ef42b..58fb52126 100644 --- a/app/src/main/java/foundation/e/apps/data/provider/AgeRatingProvider.kt +++ b/app/src/main/java/foundation/e/apps/data/provider/AgeRatingProvider.kt @@ -47,6 +47,7 @@ import foundation.e.apps.data.blockedApps.BlockedAppRepository import foundation.e.apps.data.enums.Source import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.pkg.AppLoungePackageManager +import foundation.e.apps.data.install.toInstallationSource import foundation.e.apps.data.login.exceptions.GPlayLoginException import foundation.e.apps.data.parentalcontrol.ContentRatingDao import foundation.e.apps.data.parentalcontrol.ContentRatingEntity @@ -224,7 +225,7 @@ class AgeRatingProvider : ContentProvider() { private suspend fun isAppValidRegardingAge(packageName: String): Boolean? { val fakeAppInstall = AppInstall( packageName = packageName, - source = Source.PLAY_STORE + source = Source.PLAY_STORE.toInstallationSource() ) val validateResult = validateAppAgeLimitUseCase(fakeAppInstall) saveContentRatingIfInvalid(validateResult, packageName) @@ -250,7 +251,7 @@ class AgeRatingProvider : ContentProvider() { private suspend fun isAppValidRegardingNSFW(packageName: String): Boolean { val fakeAppInstall = AppInstall( packageName = packageName, - source = Source.OPEN_SOURCE, + source = Source.OPEN_SOURCE.toInstallationSource(), ) val validateResult = validateAppAgeLimitUseCase(fakeAppInstall) return validateResult.data?.isValid ?: false diff --git a/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt b/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt index 691401e21..a88a1350b 100644 --- a/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt +++ b/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt @@ -24,9 +24,9 @@ import foundation.e.apps.contract.ParentalControlContract.Age import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.application.apps.AppsApi import foundation.e.apps.data.blockedApps.BlockedAppRepository -import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.enums.Type import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.InstallationSource +import foundation.e.apps.data.installation.model.InstallationType import foundation.e.apps.data.parentalcontrol.ContentRatingDao import foundation.e.apps.data.parentalcontrol.ParentalControlRepository import foundation.e.apps.data.parentalcontrol.ParentalControlRepository.Companion.KEY_PARENTAL_GUIDANCE @@ -91,17 +91,17 @@ class ValidateAppAgeLimitUseCase @Inject constructor( } private fun isGitlabApp(app: AppInstall): Boolean { - return app.source == Source.SYSTEM_APP + return app.source == InstallationSource.SYSTEM_APP } private fun isCleanApkApp(app: AppInstall): Boolean { return app.id.isNotBlank() - && (app.source == Source.PWA || app.source == Source.OPEN_SOURCE) - && app.type == Type.NATIVE + && (app.source == InstallationSource.PWA || app.source == InstallationSource.OPEN_SOURCE) + && app.type == InstallationType.NATIVE } private fun isWhiteListedCleanApkApp(app: AppInstall): Boolean { - return app.source == Source.OPEN_SOURCE || app.source == Source.PWA + return app.source == InstallationSource.OPEN_SOURCE || app.source == InstallationSource.PWA } private suspend fun isNsfwAppByCleanApkApi(app: AppInstall): Boolean { @@ -140,7 +140,7 @@ class ValidateAppAgeLimitUseCase @Inject constructor( } private suspend fun hasNoContentRatingOnGPlay(app: AppInstall): Boolean { - return app.source == Source.PLAY_STORE && !verifyContentRatingExists(app) + return app.source == InstallationSource.PLAY_STORE && !verifyContentRatingExists(app) } private fun isValidAppAgeRating( diff --git a/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt b/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt index 151a18dd3..be0969793 100644 --- a/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt @@ -37,6 +37,7 @@ import foundation.e.apps.data.install.core.AppInstallationFacade import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PwaManager +import foundation.e.apps.data.install.toInstallationSource import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.system.NetworkStatusManager import foundation.e.apps.domain.application.ApplicationDomain @@ -341,7 +342,7 @@ class MainActivityViewModel @Inject constructor( ) { applicationList.forEach { val downloadingItem = appInstallList.find { fusedDownload -> - fusedDownload.source == it.source && + fusedDownload.source == it.source.toInstallationSource() && (fusedDownload.packageName == it.package_name || fusedDownload.id == it._id) } it.status = @@ -355,7 +356,7 @@ class MainActivityViewModel @Inject constructor( ): List { return homeApps.map { homeApp -> val downloadingItem = appInstallList.find { fusedDownload -> - fusedDownload.source == homeApp.source && + fusedDownload.source == homeApp.source.toInstallationSource() && (fusedDownload.packageName == homeApp.packageName || fusedDownload.id == homeApp.id) } val status = downloadingItem?.status diff --git a/app/src/test/java/foundation/e/apps/data/install/models/AppInstallTest.kt b/app/src/test/java/foundation/e/apps/data/install/models/AppInstallTest.kt index a33c9a8a3..dc1310c44 100644 --- a/app/src/test/java/foundation/e/apps/data/install/models/AppInstallTest.kt +++ b/app/src/test/java/foundation/e/apps/data/install/models/AppInstallTest.kt @@ -2,11 +2,13 @@ package foundation.e.apps.data.install.models import com.google.common.truth.Truth.assertThat import foundation.e.apps.data.cleanapk.CleanApkRetrofit -import foundation.e.apps.data.enums.Source +import foundation.e.apps.data.install.AppInstallIconUrlBuilder +import foundation.e.apps.data.installation.model.InstallationSource import foundation.e.apps.domain.model.install.Status import org.junit.Test class AppInstallTest { + private val iconUrlBuilder = AppInstallIconUrlBuilder() @Test fun isAppInstalling_matchesDownloadingStates() { @@ -38,13 +40,13 @@ class AppInstallTest { } @Test - fun getAppIconUrl_usesAssetUrlForPlayStoreAndPwa() { - val playStoreApp = AppInstall(source = Source.PLAY_STORE, iconImageUrl = "icon.png") - val pwaApp = AppInstall(source = Source.PWA, iconImageUrl = "icon2.png") - val fossApp = AppInstall(source = Source.OPEN_SOURCE, iconImageUrl = "https://example/icon") - - assertThat(playStoreApp.getAppIconUrl()).isEqualTo("${CleanApkRetrofit.ASSET_URL}icon.png") - assertThat(pwaApp.getAppIconUrl()).isEqualTo("${CleanApkRetrofit.ASSET_URL}icon2.png") - assertThat(fossApp.getAppIconUrl()).isEqualTo("https://example/icon") + fun build_usesAssetUrlForPlayStoreAndPwa() { + val playStoreApp = AppInstall(source = InstallationSource.PLAY_STORE, iconImageUrl = "icon.png") + val pwaApp = AppInstall(source = InstallationSource.PWA, iconImageUrl = "icon2.png") + val fossApp = AppInstall(source = InstallationSource.OPEN_SOURCE, iconImageUrl = "https://example/icon") + + assertThat(iconUrlBuilder.build(playStoreApp.source, playStoreApp.iconImageUrl)).isEqualTo("${CleanApkRetrofit.ASSET_URL}icon.png") + assertThat(iconUrlBuilder.build(pwaApp.source, pwaApp.iconImageUrl)).isEqualTo("${CleanApkRetrofit.ASSET_URL}icon2.png") + assertThat(iconUrlBuilder.build(fossApp.source, fossApp.iconImageUrl)).isEqualTo("https://example/icon") } } diff --git a/app/src/test/java/foundation/e/apps/data/install/pkg/PwaManagerTest.kt b/app/src/test/java/foundation/e/apps/data/install/pkg/PwaManagerTest.kt index 71a448511..5485315c5 100644 --- a/app/src/test/java/foundation/e/apps/data/install/pkg/PwaManagerTest.kt +++ b/app/src/test/java/foundation/e/apps/data/install/pkg/PwaManagerTest.kt @@ -24,6 +24,7 @@ import android.database.Cursor import android.database.MatrixCursor import android.net.Uri import androidx.test.core.app.ApplicationProvider +import foundation.e.apps.data.install.AppInstallIconUrlBuilder import io.mockk.mockk import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue @@ -43,7 +44,7 @@ class PwaManagerTest { addRow(arrayOf(null)) } registerProvider(cursor) - val manager = PwaManager(context(), mockk(relaxed = true)) + val manager = PwaManager(context(), mockk(relaxed = true), AppInstallIconUrlBuilder()) val urls = manager.getInstalledPwaUrls() @@ -56,7 +57,7 @@ class PwaManagerTest { addRow(arrayOf("1")) } registerProvider(cursor) - val manager = PwaManager(context(), mockk(relaxed = true)) + val manager = PwaManager(context(), mockk(relaxed = true), AppInstallIconUrlBuilder()) val urls = manager.getInstalledPwaUrls() diff --git a/app/src/test/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCaseTest.kt b/app/src/test/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCaseTest.kt index 5499501d4..15862ead0 100644 --- a/app/src/test/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCaseTest.kt +++ b/app/src/test/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCaseTest.kt @@ -9,14 +9,16 @@ import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.blockedApps.BlockedAppRepository import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.enums.Source -import foundation.e.apps.domain.model.install.Status import foundation.e.apps.data.enums.Type +import foundation.e.apps.data.install.toInstallationSource +import foundation.e.apps.data.install.toInstallationType import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.parentalcontrol.ContentRatingDao import foundation.e.apps.data.parentalcontrol.ParentalControlRepository import foundation.e.apps.data.parentalcontrol.fdroid.FDroidAntiFeatureRepository import foundation.e.apps.data.parentalcontrol.googleplay.GPlayContentRatingGroup import foundation.e.apps.data.parentalcontrol.googleplay.GPlayContentRatingRepository +import foundation.e.apps.domain.model.install.Status import foundation.e.apps.domain.ValidateAppAgeLimitUseCase.Companion.KEY_ANTI_FEATURES_NSFW import io.mockk.coEvery import io.mockk.every @@ -143,9 +145,9 @@ class ValidateAppAgeLimitUseCaseTest { contentRating: ContentRating = ContentRating(id = "PG", title = "Parental Guidance") ) = AppInstall( packageName = "pkg", - source = source, + source = source.toInstallationSource(), status = Status.UNAVAILABLE, - type = Type.NATIVE, + type = Type.NATIVE.toInstallationType(), contentRating = contentRating ) } diff --git a/app/src/test/java/foundation/e/apps/installProcessor/DownloadUrlRefresherTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/DownloadUrlRefresherTest.kt index 5602e02e3..04095f824 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/DownloadUrlRefresherTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/DownloadUrlRefresherTest.kt @@ -20,7 +20,6 @@ package foundation.e.apps.installProcessor import com.aurora.gplayapi.exceptions.InternalException import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.enums.Type import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.application.ApplicationRepository import foundation.e.apps.data.install.AppInstallRepository @@ -28,6 +27,8 @@ import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.core.helper.DownloadUrlRefresher +import foundation.e.apps.data.installation.model.InstallationSource +import foundation.e.apps.data.installation.model.InstallationType import foundation.e.apps.data.playstore.utils.GplayHttpRequestException import foundation.e.apps.domain.model.install.Status import io.mockk.coEvery @@ -171,8 +172,8 @@ class DownloadUrlRefresherTest { } private fun createNativeInstall(isFree: Boolean = true) = AppInstall( - type = Type.NATIVE, - source = Source.PLAY_STORE, + type = InstallationType.NATIVE, + source = InstallationSource.PLAY_STORE, id = "123", status = Status.AWAITING, packageName = "com.example.app", diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt index c4c4cb82f..33ccd3635 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt @@ -26,7 +26,6 @@ import com.google.common.util.concurrent.Futures import foundation.e.apps.R import foundation.e.apps.data.application.ApplicationRepository import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.enums.Type import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.AppInstallRepository @@ -38,6 +37,8 @@ import foundation.e.apps.data.install.core.helper.DevicePreconditions import foundation.e.apps.data.install.core.helper.DownloadUrlRefresher import foundation.e.apps.data.install.core.helper.PreEnqueueChecker import foundation.e.apps.data.install.core.InstallationEnqueuer +import foundation.e.apps.data.installation.model.InstallationSource +import foundation.e.apps.data.installation.model.InstallationType import foundation.e.apps.data.install.workmanager.InstallWorkManager import foundation.e.apps.data.install.wrapper.NetworkStatusChecker import foundation.e.apps.data.install.wrapper.StorageSpaceChecker @@ -477,7 +478,7 @@ class InstallationEnqueuerTest { } private fun createPwaInstall(isFree: Boolean = true) = AppInstall( - type = Type.PWA, + type = InstallationType.PWA, id = "123", status = Status.AWAITING, downloadURLList = mutableListOf("apk"), @@ -486,8 +487,8 @@ class InstallationEnqueuerTest { ) private fun createNativeInstall(isFree: Boolean = true) = AppInstall( - type = Type.NATIVE, - source = Source.PLAY_STORE, + type = InstallationType.NATIVE, + source = InstallationSource.PLAY_STORE, id = "123", status = Status.AWAITING, packageName = "com.example.app", diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallationRequestTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationRequestTest.kt index 38495a31a..a660b6ac6 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/InstallationRequestTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationRequestTest.kt @@ -23,6 +23,8 @@ import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Type import foundation.e.apps.data.install.core.InstallationRequest +import foundation.e.apps.data.installation.model.InstallationSource +import foundation.e.apps.data.installation.model.InstallationType import foundation.e.apps.domain.model.install.Status import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue @@ -56,11 +58,11 @@ class InstallationRequestTest { val appInstall = installationRequest.create(application) assertEquals("123", appInstall.id) - assertEquals(Source.PLAY_STORE, appInstall.source) + assertEquals(InstallationSource.PLAY_STORE, appInstall.source) assertEquals(Status.AWAITING, appInstall.status) assertEquals("Example", appInstall.name) assertEquals("com.example.app", appInstall.packageName) - assertEquals(Type.NATIVE, appInstall.type) + assertEquals(InstallationType.NATIVE, appInstall.type) assertEquals("icon.png", appInstall.iconImageUrl) assertEquals(42, appInstall.versionCode) assertEquals(1, appInstall.offerType) diff --git a/app/src/test/java/foundation/e/apps/installProcessor/PreEnqueueCheckerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/PreEnqueueCheckerTest.kt index e19498e77..51e738a20 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/PreEnqueueCheckerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/PreEnqueueCheckerTest.kt @@ -18,14 +18,14 @@ package foundation.e.apps.installProcessor -import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.enums.Type import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.core.helper.AgeLimiter import foundation.e.apps.data.install.core.helper.DevicePreconditions import foundation.e.apps.data.install.core.helper.DownloadUrlRefresher import foundation.e.apps.data.install.core.helper.PreEnqueueChecker +import foundation.e.apps.data.installation.model.InstallationSource +import foundation.e.apps.data.installation.model.InstallationType import foundation.e.apps.domain.model.install.Status import io.mockk.coEvery import io.mockk.coVerify @@ -127,7 +127,7 @@ class PreEnqueueCheckerTest { } private fun createPwaInstall() = AppInstall( - type = Type.PWA, + type = InstallationType.PWA, id = "123", status = Status.AWAITING, downloadURLList = mutableListOf("apk"), @@ -135,8 +135,8 @@ class PreEnqueueCheckerTest { ) private fun createNativeInstall() = AppInstall( - type = Type.NATIVE, - source = Source.PLAY_STORE, + type = InstallationType.NATIVE, + source = InstallationSource.PLAY_STORE, id = "123", status = Status.AWAITING, packageName = "com.example.app" diff --git a/data/src/main/java/foundation/e/apps/data/installation/model/InstallationSource.kt b/data/src/main/java/foundation/e/apps/data/installation/model/InstallationSource.kt new file mode 100644 index 000000000..98c515130 --- /dev/null +++ b/data/src/main/java/foundation/e/apps/data/installation/model/InstallationSource.kt @@ -0,0 +1,8 @@ +package foundation.e.apps.data.installation.model + +enum class InstallationSource { + OPEN_SOURCE, + PWA, + SYSTEM_APP, + PLAY_STORE +} diff --git a/data/src/main/java/foundation/e/apps/data/installation/model/InstallationType.kt b/data/src/main/java/foundation/e/apps/data/installation/model/InstallationType.kt new file mode 100644 index 000000000..495c91cbc --- /dev/null +++ b/data/src/main/java/foundation/e/apps/data/installation/model/InstallationType.kt @@ -0,0 +1,6 @@ +package foundation.e.apps.data.installation.model + +enum class InstallationType { + NATIVE, + PWA +} -- GitLab From 2e637d9d6224fa846ee539d5a6bfd61a4850c88b Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Mon, 30 Mar 2026 15:30:03 +0600 Subject: [PATCH 37/43] refactor(install): move AppInstall entity to data module --- .../e/apps/data/application/ApplicationRepository.kt | 2 +- .../java/foundation/e/apps/data/application/UpdatesDao.kt | 2 +- .../e/apps/data/application/downloadInfo/DownloadInfoApi.kt | 2 +- .../apps/data/application/downloadInfo/DownloadInfoApiImpl.kt | 2 +- .../e/apps/data/database/install/AppInstallDatabase.kt | 2 +- app/src/main/java/foundation/e/apps/data/event/AppEvent.kt | 2 +- .../main/java/foundation/e/apps/data/install/AppInstallDAO.kt | 2 +- .../foundation/e/apps/data/install/AppInstallRepository.kt | 2 +- .../main/java/foundation/e/apps/data/install/AppManager.kt | 2 +- .../java/foundation/e/apps/data/install/AppManagerImpl.kt | 2 +- .../java/foundation/e/apps/data/install/AppManagerWrapper.kt | 2 +- .../e/apps/data/install/core/AppInstallationFacade.kt | 2 +- .../e/apps/data/install/core/InstallationEnqueuer.kt | 3 +-- .../e/apps/data/install/core/InstallationProcessor.kt | 2 +- .../e/apps/data/install/core/InstallationRequest.kt | 3 +-- .../foundation/e/apps/data/install/core/helper/AgeLimiter.kt | 3 +-- .../e/apps/data/install/core/helper/DevicePreconditions.kt | 3 +-- .../e/apps/data/install/core/helper/DownloadUrlRefresher.kt | 3 +-- .../data/install/core/helper/InstallationCompletionHandler.kt | 3 +-- .../e/apps/data/install/core/helper/PreEnqueueChecker.kt | 2 +- .../e/apps/data/install/download/DownloadManagerUtils.kt | 2 +- .../data/install/notification/StorageNotificationManager.kt | 2 +- .../e/apps/data/install/pkg/AppLoungePackageManager.kt | 2 +- .../java/foundation/e/apps/data/install/pkg/PwaManager.kt | 2 +- .../e/apps/data/install/workmanager/InstallOrchestrator.kt | 2 +- .../e/apps/data/install/workmanager/InstallWorkManager.kt | 2 +- .../e/apps/data/install/wrapper/StorageSpaceChecker.kt | 2 +- .../foundation/e/apps/data/install/wrapper/UpdatesTracker.kt | 2 +- .../java/foundation/e/apps/data/provider/AgeRatingProvider.kt | 3 +-- .../e/apps/data/receivers/DumpAppInstallStatusReceiver.kt | 2 +- .../java/foundation/e/apps/data/system/StorageComputer.kt | 2 +- .../foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt | 2 +- app/src/main/java/foundation/e/apps/ui/MainActivity.kt | 3 +-- .../main/java/foundation/e/apps/ui/MainActivityViewModel.kt | 3 +-- .../foundation/e/apps/ui/application/ApplicationViewModel.kt | 2 +- .../foundation/e/apps/ui/compose/state/InstallStatusStream.kt | 3 +-- .../main/java/foundation/e/apps/ui/search/SearchFragment.kt | 3 +-- .../main/java/foundation/e/apps/ui/updates/UpdatesFragment.kt | 3 +-- .../e/apps/data/application/ApplicationRepositoryHomeTest.kt | 2 +- .../test/java/foundation/e/apps/data/event/AppEventTest.kt | 2 +- .../e/apps/data/install/AppManagerWrapperProgressTest.kt | 2 +- .../foundation/e/apps/data/install/models/AppInstallTest.kt | 1 + .../e/apps/data/install/workmanager/InstallWorkManagerTest.kt | 2 +- .../e/apps/domain/ValidateAppAgeLimitUseCaseTest.kt | 2 +- .../e/apps/domain/install/InstallAppByIdUseCaseTest.kt | 2 +- .../foundation/e/apps/fusedManager/AppManagerWrapperTest.kt | 2 +- .../java/foundation/e/apps/fusedManager/FakeAppManager.kt | 2 +- .../e/apps/install/workmanager/InstallOrchestratorTest.kt | 2 +- .../java/foundation/e/apps/installProcessor/AgeLimiterTest.kt | 2 +- .../e/apps/installProcessor/AppInstallationFacadeTest.kt | 2 +- .../e/apps/installProcessor/DevicePreconditionsTest.kt | 2 +- .../e/apps/installProcessor/DownloadUrlRefresherTest.kt | 2 +- .../foundation/e/apps/installProcessor/FakeAppInstallDAO.kt | 2 +- .../e/apps/installProcessor/FakeAppManagerWrapper.kt | 2 +- .../installProcessor/InstallationCompletionHandlerTest.kt | 2 +- .../e/apps/installProcessor/InstallationEnqueuerTest.kt | 2 +- .../e/apps/installProcessor/InstallationProcessorTest.kt | 2 +- .../e/apps/installProcessor/PreEnqueueCheckerTest.kt | 2 +- .../e/apps/ui/compose/state/InstallStatusReconcilerTest.kt | 2 +- .../e/apps/ui/compose/state/InstallStatusStreamTest.kt | 2 +- .../foundation/e/apps/data/installation/model}/AppInstall.kt | 4 +--- 61 files changed, 61 insertions(+), 74 deletions(-) rename {app/src/main/java/foundation/e/apps/data/install/models => data/src/main/java/foundation/e/apps/data/installation/model}/AppInstall.kt (89%) diff --git a/app/src/main/java/foundation/e/apps/data/application/ApplicationRepository.kt b/app/src/main/java/foundation/e/apps/data/application/ApplicationRepository.kt index 5debc9706..5c6d31168 100644 --- a/app/src/main/java/foundation/e/apps/data/application/ApplicationRepository.kt +++ b/app/src/main/java/foundation/e/apps/data/application/ApplicationRepository.kt @@ -33,7 +33,7 @@ import foundation.e.apps.data.enums.FilterLevel import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.enums.Source import foundation.e.apps.data.handleNetworkResult -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.domain.model.install.Status import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope diff --git a/app/src/main/java/foundation/e/apps/data/application/UpdatesDao.kt b/app/src/main/java/foundation/e/apps/data/application/UpdatesDao.kt index 5b0f58d66..e99c0cd03 100644 --- a/app/src/main/java/foundation/e/apps/data/application/UpdatesDao.kt +++ b/app/src/main/java/foundation/e/apps/data/application/UpdatesDao.kt @@ -18,7 +18,7 @@ package foundation.e.apps.data.application import foundation.e.apps.data.application.data.Application -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall object UpdatesDao { private val _appsAwaitingForUpdate: MutableList = mutableListOf() diff --git a/app/src/main/java/foundation/e/apps/data/application/downloadInfo/DownloadInfoApi.kt b/app/src/main/java/foundation/e/apps/data/application/downloadInfo/DownloadInfoApi.kt index b29dbb5f4..cb85ad2ee 100644 --- a/app/src/main/java/foundation/e/apps/data/application/downloadInfo/DownloadInfoApi.kt +++ b/app/src/main/java/foundation/e/apps/data/application/downloadInfo/DownloadInfoApi.kt @@ -20,7 +20,7 @@ package foundation.e.apps.data.application.downloadInfo import foundation.e.apps.data.cleanapk.data.download.Download import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import retrofit2.Response interface DownloadInfoApi { diff --git a/app/src/main/java/foundation/e/apps/data/application/downloadInfo/DownloadInfoApiImpl.kt b/app/src/main/java/foundation/e/apps/data/application/downloadInfo/DownloadInfoApiImpl.kt index 47a1b720c..e79e6daf4 100644 --- a/app/src/main/java/foundation/e/apps/data/application/downloadInfo/DownloadInfoApiImpl.kt +++ b/app/src/main/java/foundation/e/apps/data/application/downloadInfo/DownloadInfoApiImpl.kt @@ -22,7 +22,7 @@ import foundation.e.apps.data.AppSourcesContainer import foundation.e.apps.data.cleanapk.CleanApkDownloadInfoFetcher import foundation.e.apps.data.enums.Source import foundation.e.apps.data.handleNetworkResult -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import javax.inject.Inject class DownloadInfoApiImpl @Inject constructor( diff --git a/app/src/main/java/foundation/e/apps/data/database/install/AppInstallDatabase.kt b/app/src/main/java/foundation/e/apps/data/database/install/AppInstallDatabase.kt index 011d4af7c..3d4e4fde5 100644 --- a/app/src/main/java/foundation/e/apps/data/database/install/AppInstallDatabase.kt +++ b/app/src/main/java/foundation/e/apps/data/database/install/AppInstallDatabase.kt @@ -7,7 +7,7 @@ import androidx.room.RoomDatabase import androidx.room.TypeConverters import foundation.e.apps.data.database.AppDatabase import foundation.e.apps.data.install.AppInstallDAO -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall @Database(entities = [AppInstall::class], version = 6, exportSchema = false) @TypeConverters(AppInstallConverter::class) diff --git a/app/src/main/java/foundation/e/apps/data/event/AppEvent.kt b/app/src/main/java/foundation/e/apps/data/event/AppEvent.kt index b12a12ea8..041eb165c 100644 --- a/app/src/main/java/foundation/e/apps/data/event/AppEvent.kt +++ b/app/src/main/java/foundation/e/apps/data/event/AppEvent.kt @@ -22,7 +22,7 @@ package foundation.e.apps.data.event import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.enums.ResultStatus -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.domain.model.User import kotlinx.coroutines.CompletableDeferred diff --git a/app/src/main/java/foundation/e/apps/data/install/AppInstallDAO.kt b/app/src/main/java/foundation/e/apps/data/install/AppInstallDAO.kt index 397f993cf..d661594f0 100644 --- a/app/src/main/java/foundation/e/apps/data/install/AppInstallDAO.kt +++ b/app/src/main/java/foundation/e/apps/data/install/AppInstallDAO.kt @@ -7,7 +7,7 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Update -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import kotlinx.coroutines.flow.Flow @Dao diff --git a/app/src/main/java/foundation/e/apps/data/install/AppInstallRepository.kt b/app/src/main/java/foundation/e/apps/data/install/AppInstallRepository.kt index ddebd3709..556caa19e 100644 --- a/app/src/main/java/foundation/e/apps/data/install/AppInstallRepository.kt +++ b/app/src/main/java/foundation/e/apps/data/install/AppInstallRepository.kt @@ -3,7 +3,7 @@ package foundation.e.apps.data.install import androidx.lifecycle.LiveData import androidx.lifecycle.asFlow import foundation.e.apps.OpenForTesting -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock diff --git a/app/src/main/java/foundation/e/apps/data/install/AppManager.kt b/app/src/main/java/foundation/e/apps/data/install/AppManager.kt index d09415b79..b5267bfc9 100644 --- a/app/src/main/java/foundation/e/apps/data/install/AppManager.kt +++ b/app/src/main/java/foundation/e/apps/data/install/AppManager.kt @@ -19,7 +19,7 @@ package foundation.e.apps.data.install import androidx.lifecycle.LiveData -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.domain.model.install.Status import java.io.File diff --git a/app/src/main/java/foundation/e/apps/data/install/AppManagerImpl.kt b/app/src/main/java/foundation/e/apps/data/install/AppManagerImpl.kt index 3596d4ad2..772d9e850 100644 --- a/app/src/main/java/foundation/e/apps/data/install/AppManagerImpl.kt +++ b/app/src/main/java/foundation/e/apps/data/install/AppManagerImpl.kt @@ -29,9 +29,9 @@ import androidx.lifecycle.LiveData import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R import foundation.e.apps.data.install.download.data.DownloadProgressLD -import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PwaManager +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.installation.model.InstallationType import foundation.e.apps.data.parentalcontrol.ContentRatingDao import foundation.e.apps.data.parentalcontrol.ContentRatingEntity diff --git a/app/src/main/java/foundation/e/apps/data/install/AppManagerWrapper.kt b/app/src/main/java/foundation/e/apps/data/install/AppManagerWrapper.kt index 87f9bc4ea..fab2df46f 100644 --- a/app/src/main/java/foundation/e/apps/data/install/AppManagerWrapper.kt +++ b/app/src/main/java/foundation/e/apps/data/install/AppManagerWrapper.kt @@ -25,8 +25,8 @@ import foundation.e.apps.OpenForTesting import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.fdroid.FDroidRepository import foundation.e.apps.data.install.download.data.DownloadProgress -import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.workmanager.InstallWorkManager +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.domain.model.install.Status import javax.inject.Inject import javax.inject.Singleton diff --git a/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt b/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt index 7488033dd..58af0dd2a 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt @@ -21,7 +21,7 @@ package foundation.e.apps.data.install.core import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.domain.model.install.Status import javax.inject.Inject diff --git a/app/src/main/java/foundation/e/apps/data/install/core/InstallationEnqueuer.kt b/app/src/main/java/foundation/e/apps/data/install/core/InstallationEnqueuer.kt index 9d179f8ec..1a4f75daa 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/InstallationEnqueuer.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/InstallationEnqueuer.kt @@ -24,16 +24,15 @@ import foundation.e.apps.R import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.core.helper.PreEnqueueChecker -import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.workmanager.InstallWorkManager import foundation.e.apps.data.install.wrapper.AppEventDispatcher +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.preference.PlayStoreAuthStore import foundation.e.apps.domain.model.User import foundation.e.apps.domain.preferences.SessionRepository import kotlinx.coroutines.CancellationException import timber.log.Timber import javax.inject.Inject - class InstallationEnqueuer @Inject constructor( @ApplicationContext private val context: Context, private val preEnqueueChecker: PreEnqueueChecker, diff --git a/app/src/main/java/foundation/e/apps/data/install/core/InstallationProcessor.kt b/app/src/main/java/foundation/e/apps/data/install/core/InstallationProcessor.kt index 22c2174cb..1809ab97c 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/InstallationProcessor.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/InstallationProcessor.kt @@ -23,7 +23,7 @@ import foundation.e.apps.data.install.AppInstallRepository 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.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.domain.model.install.Status import kotlinx.coroutines.CancellationException import kotlinx.coroutines.DelicateCoroutinesApi diff --git a/app/src/main/java/foundation/e/apps/data/install/core/InstallationRequest.kt b/app/src/main/java/foundation/e/apps/data/install/core/InstallationRequest.kt index 84d3e4ef4..ca0d0044b 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/InstallationRequest.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/InstallationRequest.kt @@ -20,12 +20,11 @@ package foundation.e.apps.data.install.core import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.toInstallationSource import foundation.e.apps.data.install.toInstallationType +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.installation.model.InstallationType import javax.inject.Inject - class InstallationRequest @Inject constructor() { fun create(application: Application): AppInstall { val appInstall = AppInstall( diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/AgeLimiter.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/AgeLimiter.kt index c8f77bc81..ee17e8162 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/AgeLimiter.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/AgeLimiter.kt @@ -22,14 +22,13 @@ import foundation.e.apps.R import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.wrapper.AppEventDispatcher import foundation.e.apps.data.install.wrapper.ParentalControlAuthGateway +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.domain.ValidateAppAgeLimitUseCase import foundation.e.apps.domain.model.ContentRatingValidity import kotlinx.coroutines.CompletableDeferred import javax.inject.Inject - class AgeLimiter @Inject constructor( private val validateAppAgeLimitUseCase: ValidateAppAgeLimitUseCase, private val appManagerWrapper: AppManagerWrapper, diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/DevicePreconditions.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/DevicePreconditions.kt index 3ba545b68..6fd979dad 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/DevicePreconditions.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/DevicePreconditions.kt @@ -21,14 +21,13 @@ package foundation.e.apps.data.install.core.helper import foundation.e.apps.R import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager import foundation.e.apps.data.install.wrapper.AppEventDispatcher import foundation.e.apps.data.install.wrapper.NetworkStatusChecker import foundation.e.apps.data.install.wrapper.StorageSpaceChecker +import foundation.e.apps.data.installation.model.AppInstall import timber.log.Timber import javax.inject.Inject - class DevicePreconditions @Inject constructor( private val appManagerWrapper: AppManagerWrapper, private val appEventDispatcher: AppEventDispatcher, diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/DownloadUrlRefresher.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/DownloadUrlRefresher.kt index 868b56ba7..c9443bd46 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/DownloadUrlRefresher.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/DownloadUrlRefresher.kt @@ -26,14 +26,13 @@ import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.toAppSource import foundation.e.apps.data.install.wrapper.AppEventDispatcher +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.playstore.utils.GplayHttpRequestException import kotlinx.coroutines.CancellationException import timber.log.Timber import javax.inject.Inject - class DownloadUrlRefresher @Inject constructor( private val applicationRepository: ApplicationRepository, private val appInstallRepository: AppInstallRepository, diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationCompletionHandler.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationCompletionHandler.kt index 0fec3c300..6deca951d 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationCompletionHandler.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationCompletionHandler.kt @@ -23,9 +23,9 @@ import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall 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.preference.PlayStoreAuthStore import foundation.e.apps.data.utils.getFormattedString import foundation.e.apps.domain.model.install.Status @@ -33,7 +33,6 @@ import java.text.NumberFormat import java.util.Date import java.util.Locale import javax.inject.Inject - class InstallationCompletionHandler @Inject constructor( @ApplicationContext private val context: Context, private val appInstallRepository: AppInstallRepository, diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/PreEnqueueChecker.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/PreEnqueueChecker.kt index 056e73aa6..4fc3687f6 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/PreEnqueueChecker.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/PreEnqueueChecker.kt @@ -19,7 +19,7 @@ package foundation.e.apps.data.install.core.helper import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.installation.model.InstallationType import timber.log.Timber import javax.inject.Inject diff --git a/app/src/main/java/foundation/e/apps/data/install/download/DownloadManagerUtils.kt b/app/src/main/java/foundation/e/apps/data/install/download/DownloadManagerUtils.kt index 4446a876e..6fa8541ff 100644 --- a/app/src/main/java/foundation/e/apps/data/install/download/DownloadManagerUtils.kt +++ b/app/src/main/java/foundation/e/apps/data/install/download/DownloadManagerUtils.kt @@ -26,8 +26,8 @@ import foundation.e.apps.data.di.qualifiers.IoCoroutineScope import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.event.EventBus import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall 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.domain.model.install.Status import kotlinx.coroutines.CoroutineScope diff --git a/app/src/main/java/foundation/e/apps/data/install/notification/StorageNotificationManager.kt b/app/src/main/java/foundation/e/apps/data/install/notification/StorageNotificationManager.kt index 77c89ee35..9ef06f905 100644 --- a/app/src/main/java/foundation/e/apps/data/install/notification/StorageNotificationManager.kt +++ b/app/src/main/java/foundation/e/apps/data/install/notification/StorageNotificationManager.kt @@ -30,7 +30,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R import foundation.e.apps.data.DownloadManager import foundation.e.apps.data.di.system.NotificationManagerModule -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.system.StorageComputer import javax.inject.Inject diff --git a/app/src/main/java/foundation/e/apps/data/install/pkg/AppLoungePackageManager.kt b/app/src/main/java/foundation/e/apps/data/install/pkg/AppLoungePackageManager.kt index 43aa31de2..cbf256259 100644 --- a/app/src/main/java/foundation/e/apps/data/install/pkg/AppLoungePackageManager.kt +++ b/app/src/main/java/foundation/e/apps/data/install/pkg/AppLoungePackageManager.kt @@ -32,7 +32,7 @@ import android.os.Build import androidx.core.content.pm.PackageInfoCompat import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.OpenForTesting -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.installation.model.InstallationSource import foundation.e.apps.data.installation.model.InstallationType import foundation.e.apps.domain.model.install.Status diff --git a/app/src/main/java/foundation/e/apps/data/install/pkg/PwaManager.kt b/app/src/main/java/foundation/e/apps/data/install/pkg/PwaManager.kt index a522373f1..8115341bd 100644 --- a/app/src/main/java/foundation/e/apps/data/install/pkg/PwaManager.kt +++ b/app/src/main/java/foundation/e/apps/data/install/pkg/PwaManager.kt @@ -16,7 +16,7 @@ import foundation.e.apps.OpenForTesting import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.install.AppInstallIconUrlBuilder import foundation.e.apps.data.install.AppInstallRepository -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.domain.model.install.Status import kotlinx.coroutines.delay import timber.log.Timber diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallOrchestrator.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallOrchestrator.kt index a5f6718a0..459746729 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallOrchestrator.kt +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallOrchestrator.kt @@ -26,7 +26,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.data.di.qualifiers.IoCoroutineScope import foundation.e.apps.data.install.AppInstallDAO import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.domain.model.install.Status import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallWorkManager.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallWorkManager.kt index 90c5e1771..27bccf90b 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallWorkManager.kt +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallWorkManager.kt @@ -7,7 +7,7 @@ import androidx.work.OneTimeWorkRequestBuilder import androidx.work.Operation import androidx.work.OutOfQuotaPolicy import androidx.work.WorkManager -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import timber.log.Timber object InstallWorkManager { diff --git a/app/src/main/java/foundation/e/apps/data/install/wrapper/StorageSpaceChecker.kt b/app/src/main/java/foundation/e/apps/data/install/wrapper/StorageSpaceChecker.kt index e4b330af9..e8a3eaa6b 100644 --- a/app/src/main/java/foundation/e/apps/data/install/wrapper/StorageSpaceChecker.kt +++ b/app/src/main/java/foundation/e/apps/data/install/wrapper/StorageSpaceChecker.kt @@ -18,7 +18,7 @@ package foundation.e.apps.data.install.wrapper -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.system.StorageComputer import javax.inject.Inject diff --git a/app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesTracker.kt b/app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesTracker.kt index 26bf2e1ba..18d71a0f1 100644 --- a/app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesTracker.kt +++ b/app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesTracker.kt @@ -19,7 +19,7 @@ package foundation.e.apps.data.install.wrapper import foundation.e.apps.data.application.UpdatesDao -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import javax.inject.Inject interface UpdatesTracker { diff --git a/app/src/main/java/foundation/e/apps/data/provider/AgeRatingProvider.kt b/app/src/main/java/foundation/e/apps/data/provider/AgeRatingProvider.kt index 58fb52126..d41184975 100644 --- a/app/src/main/java/foundation/e/apps/data/provider/AgeRatingProvider.kt +++ b/app/src/main/java/foundation/e/apps/data/provider/AgeRatingProvider.kt @@ -45,9 +45,9 @@ import foundation.e.apps.contract.ParentalControlContract.TOTAL_PACKAGE_NUMBER import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.blockedApps.BlockedAppRepository import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.toInstallationSource +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.login.exceptions.GPlayLoginException import foundation.e.apps.data.parentalcontrol.ContentRatingDao import foundation.e.apps.data.parentalcontrol.ContentRatingEntity @@ -65,7 +65,6 @@ import kotlinx.coroutines.awaitAll import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import timber.log.Timber - class AgeRatingProvider : ContentProvider() { @EntryPoint @InstallIn(SingletonComponent::class) diff --git a/app/src/main/java/foundation/e/apps/data/receivers/DumpAppInstallStatusReceiver.kt b/app/src/main/java/foundation/e/apps/data/receivers/DumpAppInstallStatusReceiver.kt index 9cdf33596..2593c3f7d 100644 --- a/app/src/main/java/foundation/e/apps/data/receivers/DumpAppInstallStatusReceiver.kt +++ b/app/src/main/java/foundation/e/apps/data/receivers/DumpAppInstallStatusReceiver.kt @@ -29,7 +29,7 @@ import dagger.hilt.android.AndroidEntryPoint import foundation.e.apps.data.Constants import foundation.e.apps.data.DownloadManager import foundation.e.apps.data.install.AppInstallRepository -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.system.NetworkStatusManager import foundation.e.apps.data.system.StorageComputer import foundation.e.apps.domain.model.install.Status diff --git a/app/src/main/java/foundation/e/apps/data/system/StorageComputer.kt b/app/src/main/java/foundation/e/apps/data/system/StorageComputer.kt index a1f1a50ef..06cd89dd6 100644 --- a/app/src/main/java/foundation/e/apps/data/system/StorageComputer.kt +++ b/app/src/main/java/foundation/e/apps/data/system/StorageComputer.kt @@ -19,7 +19,7 @@ package foundation.e.apps.data.system import android.os.Environment import android.os.StatFs -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import java.text.CharacterIterator import java.text.StringCharacterIterator import java.util.Locale diff --git a/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt b/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt index a88a1350b..2b450ab80 100644 --- a/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt +++ b/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt @@ -24,7 +24,7 @@ import foundation.e.apps.contract.ParentalControlContract.Age import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.application.apps.AppsApi import foundation.e.apps.data.blockedApps.BlockedAppRepository -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.installation.model.InstallationSource import foundation.e.apps.data.installation.model.InstallationType import foundation.e.apps.data.parentalcontrol.ContentRatingDao diff --git a/app/src/main/java/foundation/e/apps/ui/MainActivity.kt b/app/src/main/java/foundation/e/apps/ui/MainActivity.kt index e972acd6d..205b84c65 100644 --- a/app/src/main/java/foundation/e/apps/ui/MainActivity.kt +++ b/app/src/main/java/foundation/e/apps/ui/MainActivity.kt @@ -50,8 +50,8 @@ import foundation.e.apps.data.Constants import foundation.e.apps.data.enums.Source import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.event.EventBus -import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.updates.UpdatesNotifier +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.login.core.StoreType import foundation.e.apps.data.system.ParentalControlAuthenticator import foundation.e.apps.databinding.ActivityMainBinding @@ -72,7 +72,6 @@ import kotlinx.coroutines.suspendCancellableCoroutine import timber.log.Timber import kotlin.coroutines.cancellation.CancellationException import kotlin.coroutines.resume - @AndroidEntryPoint class MainActivity : AppCompatActivity() { private lateinit var signInViewModel: SignInViewModel diff --git a/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt b/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt index be0969793..512bebe82 100644 --- a/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt @@ -34,10 +34,10 @@ import foundation.e.apps.data.enums.isInitialized import foundation.e.apps.data.enums.isUnFiltered import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.core.AppInstallationFacade -import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PwaManager import foundation.e.apps.data.install.toInstallationSource +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.system.NetworkStatusManager import foundation.e.apps.domain.application.ApplicationDomain @@ -50,7 +50,6 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import javax.inject.Inject - @HiltViewModel class MainActivityViewModel @Inject constructor( private val applicationRepository: ApplicationRepository, diff --git a/app/src/main/java/foundation/e/apps/ui/application/ApplicationViewModel.kt b/app/src/main/java/foundation/e/apps/ui/application/ApplicationViewModel.kt index de36a579a..186eaa407 100644 --- a/app/src/main/java/foundation/e/apps/ui/application/ApplicationViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/application/ApplicationViewModel.kt @@ -35,7 +35,7 @@ import foundation.e.apps.data.event.EventBus import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.download.data.DownloadProgress import foundation.e.apps.data.install.download.data.DownloadProgressLD -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.parentalcontrol.fdroid.FDroidAntiFeatureRepository import foundation.e.apps.data.playstore.PlayStoreRepository diff --git a/app/src/main/java/foundation/e/apps/ui/compose/state/InstallStatusStream.kt b/app/src/main/java/foundation/e/apps/ui/compose/state/InstallStatusStream.kt index 38bdf582a..b15c5e2ff 100644 --- a/app/src/main/java/foundation/e/apps/ui/compose/state/InstallStatusStream.kt +++ b/app/src/main/java/foundation/e/apps/ui/compose/state/InstallStatusStream.kt @@ -31,9 +31,9 @@ package foundation.e.apps.ui.compose.state import androidx.lifecycle.asFlow import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PwaManager +import foundation.e.apps.data.installation.model.AppInstall import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.delay @@ -46,7 +46,6 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.withContext import javax.inject.Inject import javax.inject.Singleton - /* * Snapshot of device install state needed for UI reconciliation. * diff --git a/app/src/main/java/foundation/e/apps/ui/search/SearchFragment.kt b/app/src/main/java/foundation/e/apps/ui/search/SearchFragment.kt index 874e6b0c7..a52243d12 100644 --- a/app/src/main/java/foundation/e/apps/ui/search/SearchFragment.kt +++ b/app/src/main/java/foundation/e/apps/ui/search/SearchFragment.kt @@ -44,8 +44,8 @@ import foundation.e.apps.R import foundation.e.apps.data.application.ApplicationInstaller import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.install.download.data.DownloadProgress -import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.pkg.PwaManager +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.system.isNetworkAvailable import foundation.e.apps.databinding.FragmentSearchBinding import foundation.e.apps.domain.model.install.Status @@ -59,7 +59,6 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import java.util.Locale import javax.inject.Inject - @AndroidEntryPoint class SearchFragment : Fragment(R.layout.fragment_search), ApplicationInstaller, SearchViewHandler.SearchViewListener { diff --git a/app/src/main/java/foundation/e/apps/ui/updates/UpdatesFragment.kt b/app/src/main/java/foundation/e/apps/ui/updates/UpdatesFragment.kt index 5637aa273..1d9f7cd90 100644 --- a/app/src/main/java/foundation/e/apps/ui/updates/UpdatesFragment.kt +++ b/app/src/main/java/foundation/e/apps/ui/updates/UpdatesFragment.kt @@ -43,10 +43,10 @@ import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.event.EventBus import foundation.e.apps.data.install.download.data.DownloadProgress -import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.pkg.PwaManager import foundation.e.apps.data.install.updates.UpdatesWorkManager import foundation.e.apps.data.install.workmanager.InstallWorkManager +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.login.exceptions.GPlayException import foundation.e.apps.data.login.exceptions.GPlayLoginException @@ -67,7 +67,6 @@ import kotlinx.coroutines.launch import timber.log.Timber import java.util.Locale import javax.inject.Inject - @AndroidEntryPoint class UpdatesFragment : TimeoutFragment(R.layout.fragment_updates), ApplicationInstaller { diff --git a/app/src/test/java/foundation/e/apps/data/application/ApplicationRepositoryHomeTest.kt b/app/src/test/java/foundation/e/apps/data/application/ApplicationRepositoryHomeTest.kt index 20921e571..02db7e932 100644 --- a/app/src/test/java/foundation/e/apps/data/application/ApplicationRepositoryHomeTest.kt +++ b/app/src/test/java/foundation/e/apps/data/application/ApplicationRepositoryHomeTest.kt @@ -38,7 +38,7 @@ import foundation.e.apps.data.enums.FilterLevel import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.enums.Source import foundation.e.apps.domain.model.install.Status -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.util.MainCoroutineRule import io.mockk.coEvery import io.mockk.coVerify diff --git a/app/src/test/java/foundation/e/apps/data/event/AppEventTest.kt b/app/src/test/java/foundation/e/apps/data/event/AppEventTest.kt index c3dd00dfb..985f9c63e 100644 --- a/app/src/test/java/foundation/e/apps/data/event/AppEventTest.kt +++ b/app/src/test/java/foundation/e/apps/data/event/AppEventTest.kt @@ -3,7 +3,7 @@ package foundation.e.apps.data.event import com.google.common.truth.Truth.assertThat import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.domain.model.User -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.ResultSupreme import kotlinx.coroutines.CompletableDeferred import org.junit.Test diff --git a/app/src/test/java/foundation/e/apps/data/install/AppManagerWrapperProgressTest.kt b/app/src/test/java/foundation/e/apps/data/install/AppManagerWrapperProgressTest.kt index 5d80897dd..b90d7cb07 100644 --- a/app/src/test/java/foundation/e/apps/data/install/AppManagerWrapperProgressTest.kt +++ b/app/src/test/java/foundation/e/apps/data/install/AppManagerWrapperProgressTest.kt @@ -20,7 +20,7 @@ package foundation.e.apps.data.install import android.content.Context import foundation.e.apps.domain.model.install.Status import foundation.e.apps.data.fdroid.FDroidRepository -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.install.download.data.DownloadProgress import io.mockk.mockk import kotlinx.coroutines.test.runTest diff --git a/app/src/test/java/foundation/e/apps/data/install/models/AppInstallTest.kt b/app/src/test/java/foundation/e/apps/data/install/models/AppInstallTest.kt index dc1310c44..ff4b00b14 100644 --- a/app/src/test/java/foundation/e/apps/data/install/models/AppInstallTest.kt +++ b/app/src/test/java/foundation/e/apps/data/install/models/AppInstallTest.kt @@ -3,6 +3,7 @@ package foundation.e.apps.data.install.models import com.google.common.truth.Truth.assertThat import foundation.e.apps.data.cleanapk.CleanApkRetrofit import foundation.e.apps.data.install.AppInstallIconUrlBuilder +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.installation.model.InstallationSource import foundation.e.apps.domain.model.install.Status import org.junit.Test diff --git a/app/src/test/java/foundation/e/apps/data/install/workmanager/InstallWorkManagerTest.kt b/app/src/test/java/foundation/e/apps/data/install/workmanager/InstallWorkManagerTest.kt index feed1e14e..422ca6804 100644 --- a/app/src/test/java/foundation/e/apps/data/install/workmanager/InstallWorkManagerTest.kt +++ b/app/src/test/java/foundation/e/apps/data/install/workmanager/InstallWorkManagerTest.kt @@ -32,7 +32,7 @@ import androidx.work.impl.WorkManagerImpl import androidx.work.impl.model.WorkSpec import androidx.work.testing.WorkManagerTestInitHelper import com.google.common.truth.Truth.assertThat -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import org.junit.After import org.junit.Before import org.junit.Test diff --git a/app/src/test/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCaseTest.kt b/app/src/test/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCaseTest.kt index 15862ead0..73be10814 100644 --- a/app/src/test/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCaseTest.kt +++ b/app/src/test/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCaseTest.kt @@ -12,7 +12,7 @@ import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Type import foundation.e.apps.data.install.toInstallationSource import foundation.e.apps.data.install.toInstallationType -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.parentalcontrol.ContentRatingDao import foundation.e.apps.data.parentalcontrol.ParentalControlRepository import foundation.e.apps.data.parentalcontrol.fdroid.FDroidAntiFeatureRepository diff --git a/app/src/test/java/foundation/e/apps/domain/install/InstallAppByIdUseCaseTest.kt b/app/src/test/java/foundation/e/apps/domain/install/InstallAppByIdUseCaseTest.kt index b39ff53bd..002aa2b0c 100644 --- a/app/src/test/java/foundation/e/apps/domain/install/InstallAppByIdUseCaseTest.kt +++ b/app/src/test/java/foundation/e/apps/domain/install/InstallAppByIdUseCaseTest.kt @@ -22,7 +22,7 @@ import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.Source import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.core.AppInstallationFacade -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PwaManager import foundation.e.apps.domain.model.install.Status diff --git a/app/src/test/java/foundation/e/apps/fusedManager/AppManagerWrapperTest.kt b/app/src/test/java/foundation/e/apps/fusedManager/AppManagerWrapperTest.kt index fa0980eab..f4b50cb01 100644 --- a/app/src/test/java/foundation/e/apps/fusedManager/AppManagerWrapperTest.kt +++ b/app/src/test/java/foundation/e/apps/fusedManager/AppManagerWrapperTest.kt @@ -23,7 +23,7 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule import foundation.e.apps.domain.model.install.Status import foundation.e.apps.data.fdroid.FDroidRepository import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.install.workmanager.InstallWorkManager import foundation.e.apps.installProcessor.FakeAppInstallDAO import foundation.e.apps.util.MainCoroutineRule diff --git a/app/src/test/java/foundation/e/apps/fusedManager/FakeAppManager.kt b/app/src/test/java/foundation/e/apps/fusedManager/FakeAppManager.kt index d30c5555e..cfcec8e16 100644 --- a/app/src/test/java/foundation/e/apps/fusedManager/FakeAppManager.kt +++ b/app/src/test/java/foundation/e/apps/fusedManager/FakeAppManager.kt @@ -22,7 +22,7 @@ import androidx.lifecycle.LiveData import foundation.e.apps.domain.model.install.Status import foundation.e.apps.data.install.AppInstallDAO import foundation.e.apps.data.install.AppManager -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import java.io.File class FakeAppManager(private val appInstallDAO: AppInstallDAO) : AppManager { diff --git a/app/src/test/java/foundation/e/apps/install/workmanager/InstallOrchestratorTest.kt b/app/src/test/java/foundation/e/apps/install/workmanager/InstallOrchestratorTest.kt index c4bbbff14..6cd97e018 100644 --- a/app/src/test/java/foundation/e/apps/install/workmanager/InstallOrchestratorTest.kt +++ b/app/src/test/java/foundation/e/apps/install/workmanager/InstallOrchestratorTest.kt @@ -33,7 +33,7 @@ import androidx.test.core.app.ApplicationProvider import com.google.common.util.concurrent.Futures import foundation.e.apps.data.install.AppInstallDAO import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.install.workmanager.InstallOrchestrator import foundation.e.apps.data.install.workmanager.InstallWorkManager import foundation.e.apps.domain.model.install.Status diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AgeLimiterTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AgeLimiterTest.kt index 8fd9fd55b..7571a1c63 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AgeLimiterTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AgeLimiterTest.kt @@ -23,7 +23,7 @@ import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.install.core.helper.AgeLimiter import foundation.e.apps.data.install.wrapper.ParentalControlAuthGateway import foundation.e.apps.domain.ValidateAppAgeLimitUseCase diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt index 9c85d976c..4ac0061ea 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt @@ -28,7 +28,7 @@ import foundation.e.apps.data.install.core.AppInstallationFacade import foundation.e.apps.data.install.core.InstallationEnqueuer import foundation.e.apps.data.install.core.InstallationProcessor import foundation.e.apps.data.install.core.InstallationRequest -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.domain.model.install.Status import foundation.e.apps.util.MainCoroutineRule import io.mockk.coEvery diff --git a/app/src/test/java/foundation/e/apps/installProcessor/DevicePreconditionsTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/DevicePreconditionsTest.kt index a4dc646e1..612e2eacb 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/DevicePreconditionsTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/DevicePreconditionsTest.kt @@ -21,7 +21,7 @@ package foundation.e.apps.installProcessor import foundation.e.apps.R import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager import foundation.e.apps.data.install.core.helper.DevicePreconditions import foundation.e.apps.data.install.wrapper.NetworkStatusChecker diff --git a/app/src/test/java/foundation/e/apps/installProcessor/DownloadUrlRefresherTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/DownloadUrlRefresherTest.kt index 04095f824..61264274e 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/DownloadUrlRefresherTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/DownloadUrlRefresherTest.kt @@ -25,7 +25,7 @@ import foundation.e.apps.data.application.ApplicationRepository import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.install.core.helper.DownloadUrlRefresher import foundation.e.apps.data.installation.model.InstallationSource import foundation.e.apps.data.installation.model.InstallationType diff --git a/app/src/test/java/foundation/e/apps/installProcessor/FakeAppInstallDAO.kt b/app/src/test/java/foundation/e/apps/installProcessor/FakeAppInstallDAO.kt index 3a76f2e0b..3a4af9142 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/FakeAppInstallDAO.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/FakeAppInstallDAO.kt @@ -22,7 +22,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData import foundation.e.apps.domain.model.install.Status import foundation.e.apps.data.install.AppInstallDAO -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flow diff --git a/app/src/test/java/foundation/e/apps/installProcessor/FakeAppManagerWrapper.kt b/app/src/test/java/foundation/e/apps/installProcessor/FakeAppManagerWrapper.kt index a046570b3..0a656e7c6 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/FakeAppManagerWrapper.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/FakeAppManagerWrapper.kt @@ -23,7 +23,7 @@ import foundation.e.apps.domain.model.install.Status import foundation.e.apps.data.fdroid.FDroidRepository import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.AppManager -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import kotlinx.coroutines.delay class FakeAppManagerWrapper( diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallationCompletionHandlerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationCompletionHandlerTest.kt index 43cc6a355..d6f7955d0 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/InstallationCompletionHandlerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationCompletionHandlerTest.kt @@ -23,7 +23,7 @@ import com.aurora.gplayapi.data.models.AuthData import foundation.e.apps.R import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.install.core.helper.InstallationCompletionHandler import foundation.e.apps.data.install.wrapper.UpdatesNotificationSender import foundation.e.apps.data.install.wrapper.UpdatesTracker diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt index 33ccd3635..1abda6c7a 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt @@ -30,7 +30,7 @@ import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager import foundation.e.apps.data.install.core.helper.AgeLimiter import foundation.e.apps.data.install.core.helper.DevicePreconditions diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt index 8212c636e..cbe892f16 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt @@ -24,7 +24,7 @@ import foundation.e.apps.data.fdroid.FDroidRepository import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.download.DownloadManagerUtils -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.install.core.InstallationProcessor import foundation.e.apps.data.install.core.helper.InstallationCompletionHandler import foundation.e.apps.domain.model.install.Status diff --git a/app/src/test/java/foundation/e/apps/installProcessor/PreEnqueueCheckerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/PreEnqueueCheckerTest.kt index 51e738a20..9b849dd35 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/PreEnqueueCheckerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/PreEnqueueCheckerTest.kt @@ -19,7 +19,7 @@ package foundation.e.apps.installProcessor import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.install.core.helper.AgeLimiter import foundation.e.apps.data.install.core.helper.DevicePreconditions import foundation.e.apps.data.install.core.helper.DownloadUrlRefresher diff --git a/app/src/test/java/foundation/e/apps/ui/compose/state/InstallStatusReconcilerTest.kt b/app/src/test/java/foundation/e/apps/ui/compose/state/InstallStatusReconcilerTest.kt index 318653c4e..109378284 100644 --- a/app/src/test/java/foundation/e/apps/ui/compose/state/InstallStatusReconcilerTest.kt +++ b/app/src/test/java/foundation/e/apps/ui/compose/state/InstallStatusReconcilerTest.kt @@ -21,7 +21,7 @@ import foundation.e.apps.data.application.ApplicationRepository import foundation.e.apps.data.application.data.Application import foundation.e.apps.domain.model.install.Status import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.install.download.data.DownloadProgress import io.mockk.coEvery import io.mockk.coVerify diff --git a/app/src/test/java/foundation/e/apps/ui/compose/state/InstallStatusStreamTest.kt b/app/src/test/java/foundation/e/apps/ui/compose/state/InstallStatusStreamTest.kt index 93b3debb4..0c9faefb8 100644 --- a/app/src/test/java/foundation/e/apps/ui/compose/state/InstallStatusStreamTest.kt +++ b/app/src/test/java/foundation/e/apps/ui/compose/state/InstallStatusStreamTest.kt @@ -21,7 +21,7 @@ import android.content.pm.ApplicationInfo import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.MutableLiveData import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PwaManager import foundation.e.apps.util.MainCoroutineRule diff --git a/app/src/main/java/foundation/e/apps/data/install/models/AppInstall.kt b/data/src/main/java/foundation/e/apps/data/installation/model/AppInstall.kt similarity index 89% rename from app/src/main/java/foundation/e/apps/data/install/models/AppInstall.kt rename to data/src/main/java/foundation/e/apps/data/installation/model/AppInstall.kt index 52b76c696..cfe32dfbb 100644 --- a/app/src/main/java/foundation/e/apps/data/install/models/AppInstall.kt +++ b/data/src/main/java/foundation/e/apps/data/installation/model/AppInstall.kt @@ -1,12 +1,10 @@ -package foundation.e.apps.data.install.models +package foundation.e.apps.data.installation.model import androidx.room.Entity import androidx.room.Ignore import androidx.room.PrimaryKey import com.aurora.gplayapi.data.models.ContentRating import com.aurora.gplayapi.data.models.PlayFile -import foundation.e.apps.data.installation.model.InstallationSource -import foundation.e.apps.data.installation.model.InstallationType import foundation.e.apps.domain.model.install.Status @Entity(tableName = "FusedDownload") -- GitLab From de09ae6fec3b48896ce095f39d8ed965ea6a2f68 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Mon, 30 Mar 2026 15:36:56 +0600 Subject: [PATCH 38/43] refactor(install): move install room persistence to data module --- .../foundation/e/apps/AppLoungeApplication.kt | 3 +-- .../e/apps/data/database/AppDatabase.kt | 3 +-- .../e/apps/data/di/db/DatabaseModule.kt | 4 ++-- .../apps/data/install/AppInstallRepository.kt | 1 + .../workmanager/InstallOrchestrator.kt | 3 +-- .../compose/state/InstallStatusReconciler.kt | 2 +- .../install/AppInstallConverterTest.kt | 2 +- .../e/apps/data/system/StorageComputerTest.kt | 2 +- .../e/apps/fusedManager/FakeAppManager.kt | 2 +- .../workmanager/InstallOrchestratorTest.kt | 2 +- .../installProcessor/FakeAppInstallDAO.kt | 2 +- .../local}/AppInstallConverter.kt | 2 +- .../data/installation/local}/AppInstallDAO.kt | 2 +- .../installation/local}/AppInstallDatabase.kt | 23 ++++++++++--------- 14 files changed, 26 insertions(+), 27 deletions(-) rename {app/src/main/java/foundation/e/apps/data/database/install => data/src/main/java/foundation/e/apps/data/installation/local}/AppInstallConverter.kt (96%) rename {app/src/main/java/foundation/e/apps/data/install => data/src/main/java/foundation/e/apps/data/installation/local}/AppInstallDAO.kt (96%) rename {app/src/main/java/foundation/e/apps/data/database/install => data/src/main/java/foundation/e/apps/data/installation/local}/AppInstallDatabase.kt (52%) diff --git a/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt b/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt index 5a96dbf71..77067ef69 100644 --- a/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt +++ b/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt @@ -29,11 +29,11 @@ import dagger.hilt.android.HiltAndroidApp import foundation.e.apps.data.Constants.TAG_APP_INSTALL_STATE import foundation.e.apps.data.Constants.TAG_AUTHDATA_DUMP import foundation.e.apps.data.di.qualifiers.IoCoroutineScope -import foundation.e.apps.data.install.AppInstallDAO import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PkgManagerBR import foundation.e.apps.data.install.updates.UpdatesWorkManager import foundation.e.apps.data.install.workmanager.InstallOrchestrator +import foundation.e.apps.data.installation.local.AppInstallDAO import foundation.e.apps.data.system.CustomUncaughtExceptionHandler import foundation.e.apps.domain.model.install.Status import foundation.e.apps.domain.preferences.SessionRepository @@ -48,7 +48,6 @@ import timber.log.Timber import timber.log.Timber.Forest.plant import java.util.concurrent.Executors import javax.inject.Inject - @HiltAndroidApp @DelicateCoroutinesApi class AppLoungeApplication : Application(), Configuration.Provider { diff --git a/app/src/main/java/foundation/e/apps/data/database/AppDatabase.kt b/app/src/main/java/foundation/e/apps/data/database/AppDatabase.kt index eb7e4fa66..0b4f88721 100644 --- a/app/src/main/java/foundation/e/apps/data/database/AppDatabase.kt +++ b/app/src/main/java/foundation/e/apps/data/database/AppDatabase.kt @@ -7,18 +7,17 @@ import androidx.room.RoomDatabase import androidx.room.TypeConverters import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase -import foundation.e.apps.data.database.install.AppInstallConverter import foundation.e.apps.data.exodus.Tracker import foundation.e.apps.data.exodus.TrackerDao import foundation.e.apps.data.faultyApps.FaultyApp import foundation.e.apps.data.faultyApps.FaultyAppDao import foundation.e.apps.data.fdroid.FdroidDao import foundation.e.apps.data.fdroid.models.FdroidEntity +import foundation.e.apps.data.installation.local.AppInstallConverter import foundation.e.apps.data.parentalcontrol.ContentRatingDao import foundation.e.apps.data.parentalcontrol.ContentRatingEntity import foundation.e.apps.data.parentalcontrol.FDroidNsfwApp import foundation.e.apps.data.parentalcontrol.googleplay.GPlayContentRatingGroup - @Database( entities = [ Tracker::class, diff --git a/app/src/main/java/foundation/e/apps/data/di/db/DatabaseModule.kt b/app/src/main/java/foundation/e/apps/data/di/db/DatabaseModule.kt index 615edbd93..3dd224793 100644 --- a/app/src/main/java/foundation/e/apps/data/di/db/DatabaseModule.kt +++ b/app/src/main/java/foundation/e/apps/data/di/db/DatabaseModule.kt @@ -6,8 +6,8 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent -import foundation.e.apps.data.database.install.AppInstallDatabase -import foundation.e.apps.data.install.AppInstallDAO +import foundation.e.apps.data.installation.local.AppInstallDAO +import foundation.e.apps.data.installation.local.AppInstallDatabase import javax.inject.Singleton @Module diff --git a/app/src/main/java/foundation/e/apps/data/install/AppInstallRepository.kt b/app/src/main/java/foundation/e/apps/data/install/AppInstallRepository.kt index 556caa19e..6e814fddb 100644 --- a/app/src/main/java/foundation/e/apps/data/install/AppInstallRepository.kt +++ b/app/src/main/java/foundation/e/apps/data/install/AppInstallRepository.kt @@ -3,6 +3,7 @@ package foundation.e.apps.data.install import androidx.lifecycle.LiveData import androidx.lifecycle.asFlow import foundation.e.apps.OpenForTesting +import foundation.e.apps.data.installation.local.AppInstallDAO import foundation.e.apps.data.installation.model.AppInstall import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallOrchestrator.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallOrchestrator.kt index 459746729..9828d8f8d 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallOrchestrator.kt +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallOrchestrator.kt @@ -24,8 +24,8 @@ import androidx.work.WorkManager import androidx.work.await import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.data.di.qualifiers.IoCoroutineScope -import foundation.e.apps.data.install.AppInstallDAO import foundation.e.apps.data.install.AppManagerWrapper +import foundation.e.apps.data.installation.local.AppInstallDAO import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.domain.model.install.Status import kotlinx.coroutines.CancellationException @@ -36,7 +36,6 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject - class InstallOrchestrator @Inject constructor( @param:ApplicationContext val context: Context, @param:IoCoroutineScope private val scope: CoroutineScope, diff --git a/app/src/main/java/foundation/e/apps/ui/compose/state/InstallStatusReconciler.kt b/app/src/main/java/foundation/e/apps/ui/compose/state/InstallStatusReconciler.kt index 4546e9023..472b1d70e 100644 --- a/app/src/main/java/foundation/e/apps/ui/compose/state/InstallStatusReconciler.kt +++ b/app/src/main/java/foundation/e/apps/ui/compose/state/InstallStatusReconciler.kt @@ -22,7 +22,7 @@ import foundation.e.apps.data.application.ApplicationRepository import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.download.data.DownloadProgress -import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.installation.model.AppInstall import javax.inject.Inject import javax.inject.Singleton diff --git a/app/src/test/java/foundation/e/apps/data/database/install/AppInstallConverterTest.kt b/app/src/test/java/foundation/e/apps/data/database/install/AppInstallConverterTest.kt index acf4acaf0..5d2f946f9 100644 --- a/app/src/test/java/foundation/e/apps/data/database/install/AppInstallConverterTest.kt +++ b/app/src/test/java/foundation/e/apps/data/database/install/AppInstallConverterTest.kt @@ -3,7 +3,7 @@ package foundation.e.apps.data.database.install import com.aurora.gplayapi.data.models.ContentRating import com.aurora.gplayapi.data.models.PlayFile import com.google.common.truth.Truth.assertThat -import foundation.e.apps.data.database.install.AppInstallConverter +import foundation.e.apps.data.installation.local.AppInstallConverter import org.junit.Test class AppInstallConverterTest { diff --git a/app/src/test/java/foundation/e/apps/data/system/StorageComputerTest.kt b/app/src/test/java/foundation/e/apps/data/system/StorageComputerTest.kt index 89d0da0fb..41cc78cc8 100644 --- a/app/src/test/java/foundation/e/apps/data/system/StorageComputerTest.kt +++ b/app/src/test/java/foundation/e/apps/data/system/StorageComputerTest.kt @@ -30,7 +30,7 @@ class StorageComputerTest { @Test fun spaceMissing_matchesCalculatedDifference() { - val appInstall = foundation.e.apps.data.install.models.AppInstall(appSize = 1234) + val appInstall = foundation.e.apps.data.installation.model.AppInstall(appSize = 1234) val expected = appInstall.appSize + (500 * (1000 * 1000)) - StorageComputer.calculateAvailableDiskSpace() diff --git a/app/src/test/java/foundation/e/apps/fusedManager/FakeAppManager.kt b/app/src/test/java/foundation/e/apps/fusedManager/FakeAppManager.kt index cfcec8e16..c1084c20f 100644 --- a/app/src/test/java/foundation/e/apps/fusedManager/FakeAppManager.kt +++ b/app/src/test/java/foundation/e/apps/fusedManager/FakeAppManager.kt @@ -20,7 +20,7 @@ package foundation.e.apps.fusedManager import androidx.lifecycle.LiveData import foundation.e.apps.domain.model.install.Status -import foundation.e.apps.data.install.AppInstallDAO +import foundation.e.apps.data.installation.local.AppInstallDAO import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.installation.model.AppInstall import java.io.File diff --git a/app/src/test/java/foundation/e/apps/install/workmanager/InstallOrchestratorTest.kt b/app/src/test/java/foundation/e/apps/install/workmanager/InstallOrchestratorTest.kt index 6cd97e018..abd0ee297 100644 --- a/app/src/test/java/foundation/e/apps/install/workmanager/InstallOrchestratorTest.kt +++ b/app/src/test/java/foundation/e/apps/install/workmanager/InstallOrchestratorTest.kt @@ -31,7 +31,7 @@ import androidx.work.WorkManager import androidx.work.testing.WorkManagerTestInitHelper import androidx.test.core.app.ApplicationProvider import com.google.common.util.concurrent.Futures -import foundation.e.apps.data.install.AppInstallDAO +import foundation.e.apps.data.installation.local.AppInstallDAO import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.install.workmanager.InstallOrchestrator diff --git a/app/src/test/java/foundation/e/apps/installProcessor/FakeAppInstallDAO.kt b/app/src/test/java/foundation/e/apps/installProcessor/FakeAppInstallDAO.kt index 3a4af9142..3385dc2fa 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/FakeAppInstallDAO.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/FakeAppInstallDAO.kt @@ -21,7 +21,7 @@ package foundation.e.apps.installProcessor import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData import foundation.e.apps.domain.model.install.Status -import foundation.e.apps.data.install.AppInstallDAO +import foundation.e.apps.data.installation.local.AppInstallDAO import foundation.e.apps.data.installation.model.AppInstall import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow diff --git a/app/src/main/java/foundation/e/apps/data/database/install/AppInstallConverter.kt b/data/src/main/java/foundation/e/apps/data/installation/local/AppInstallConverter.kt similarity index 96% rename from app/src/main/java/foundation/e/apps/data/database/install/AppInstallConverter.kt rename to data/src/main/java/foundation/e/apps/data/installation/local/AppInstallConverter.kt index 3c860e005..521d98326 100644 --- a/app/src/main/java/foundation/e/apps/data/database/install/AppInstallConverter.kt +++ b/data/src/main/java/foundation/e/apps/data/installation/local/AppInstallConverter.kt @@ -1,4 +1,4 @@ -package foundation.e.apps.data.database.install +package foundation.e.apps.data.installation.local import androidx.room.TypeConverter import com.aurora.gplayapi.data.models.ContentRating diff --git a/app/src/main/java/foundation/e/apps/data/install/AppInstallDAO.kt b/data/src/main/java/foundation/e/apps/data/installation/local/AppInstallDAO.kt similarity index 96% rename from app/src/main/java/foundation/e/apps/data/install/AppInstallDAO.kt rename to data/src/main/java/foundation/e/apps/data/installation/local/AppInstallDAO.kt index d661594f0..0c81f263c 100644 --- a/app/src/main/java/foundation/e/apps/data/install/AppInstallDAO.kt +++ b/data/src/main/java/foundation/e/apps/data/installation/local/AppInstallDAO.kt @@ -1,4 +1,4 @@ -package foundation.e.apps.data.install +package foundation.e.apps.data.installation.local import androidx.lifecycle.LiveData import androidx.room.Dao diff --git a/app/src/main/java/foundation/e/apps/data/database/install/AppInstallDatabase.kt b/data/src/main/java/foundation/e/apps/data/installation/local/AppInstallDatabase.kt similarity index 52% rename from app/src/main/java/foundation/e/apps/data/database/install/AppInstallDatabase.kt rename to data/src/main/java/foundation/e/apps/data/installation/local/AppInstallDatabase.kt index 3d4e4fde5..864b1ca5a 100644 --- a/app/src/main/java/foundation/e/apps/data/database/install/AppInstallDatabase.kt +++ b/data/src/main/java/foundation/e/apps/data/installation/local/AppInstallDatabase.kt @@ -1,12 +1,10 @@ -package foundation.e.apps.data.database.install +package foundation.e.apps.data.installation.local import android.content.Context import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.TypeConverters -import foundation.e.apps.data.database.AppDatabase -import foundation.e.apps.data.install.AppInstallDAO import foundation.e.apps.data.installation.model.AppInstall @Database(entities = [AppInstall::class], version = 6, exportSchema = false) @@ -15,19 +13,22 @@ abstract class AppInstallDatabase : RoomDatabase() { abstract fun fusedDownloadDao(): AppInstallDAO companion object { - private lateinit var INSTANCE: AppInstallDatabase + private lateinit var instance: AppInstallDatabase + private val lock = Any() private const val DATABASE_NAME = "fused_database" fun getInstance(context: Context): AppInstallDatabase { - if (!Companion::INSTANCE.isInitialized) { - synchronized(AppDatabase::class) { - INSTANCE = - Room.databaseBuilder(context, AppInstallDatabase::class.java, DATABASE_NAME) - .fallbackToDestructiveMigration() - .build() + if (!Companion::instance.isInitialized) { + synchronized(lock) { + if (!Companion::instance.isInitialized) { + instance = + Room.databaseBuilder(context, AppInstallDatabase::class.java, DATABASE_NAME) + .fallbackToDestructiveMigration() + .build() + } } } - return INSTANCE + return instance } } } -- GitLab From e0677d5b753810f436db0b2709dfc51563607faa Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Mon, 30 Mar 2026 15:48:27 +0600 Subject: [PATCH 39/43] refactor(install): move install repository to data module --- .../foundation/e/apps/AppLoungeApplication.kt | 8 +-- .../e/apps/data/install/AppManagerImpl.kt | 2 +- .../install/core/InstallationProcessor.kt | 2 +- .../core/helper/DownloadUrlRefresher.kt | 2 +- .../helper/InstallationCompletionHandler.kt | 2 +- .../e/apps/data/install/pkg/PwaManager.kt | 2 +- .../receiver/PwaPlayerStatusReceiver.kt | 2 +- .../workmanager/InstallOrchestrator.kt | 8 +-- .../receivers/DumpAppInstallStatusReceiver.kt | 2 +- .../domain/install/InstallAppByIdUseCase.kt | 2 +- .../install/InstallAppByIdUseCaseTest.kt | 2 +- .../e/apps/fusedManager/FakeAppManager.kt | 4 +- .../workmanager/InstallOrchestratorTest.kt | 50 +++++++++---------- .../DownloadUrlRefresherTest.kt | 2 +- .../installProcessor/FakeAppInstallDAO.kt | 2 +- .../InstallationCompletionHandlerTest.kt | 2 +- .../InstallationEnqueuerTest.kt | 2 +- .../InstallationProcessorTest.kt | 2 +- .../repository}/AppInstallRepository.kt | 14 ++++-- 19 files changed, 60 insertions(+), 52 deletions(-) rename {app/src/main/java/foundation/e/apps/data/install => data/src/main/java/foundation/e/apps/data/installation/repository}/AppInstallRepository.kt (82%) diff --git a/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt b/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt index 77067ef69..ed694ba5b 100644 --- a/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt +++ b/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt @@ -33,7 +33,7 @@ import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PkgManagerBR import foundation.e.apps.data.install.updates.UpdatesWorkManager import foundation.e.apps.data.install.workmanager.InstallOrchestrator -import foundation.e.apps.data.installation.local.AppInstallDAO +import foundation.e.apps.data.installation.repository.AppInstallRepository import foundation.e.apps.data.system.CustomUncaughtExceptionHandler import foundation.e.apps.domain.model.install.Status import foundation.e.apps.domain.preferences.SessionRepository @@ -59,7 +59,7 @@ class AppLoungeApplication : Application(), Configuration.Provider { lateinit var workerFactory: HiltWorkerFactory @Inject - lateinit var appInstallDao: AppInstallDAO + lateinit var appInstallRepository: AppInstallRepository @Inject lateinit var uncaughtExceptionHandler: CustomUncaughtExceptionHandler @@ -139,7 +139,7 @@ class AppLoungeApplication : Application(), Configuration.Provider { private fun isRunningUnderRobolectric(): Boolean = Build.FINGERPRINT == "robolectric" private fun removeStalledInstallationFromDb() = coroutineScope.launch { - val existingInstallations = appInstallDao.getItemInInstallation().toMutableList() + val existingInstallations = appInstallRepository.getItemInInstallation().toMutableList() if (existingInstallations.isEmpty()) { return@launch } @@ -161,7 +161,7 @@ class AppLoungeApplication : Application(), Configuration.Provider { Timber.d("removing (${appInstall.packageName}) : (${appInstall.id}) from db") appInstall.status = Status.INSTALLATION_ISSUE - appInstallDao.deleteDownload(appInstall) + appInstallRepository.deleteDownload(appInstall) } } diff --git a/app/src/main/java/foundation/e/apps/data/install/AppManagerImpl.kt b/app/src/main/java/foundation/e/apps/data/install/AppManagerImpl.kt index 772d9e850..ebf2b6255 100644 --- a/app/src/main/java/foundation/e/apps/data/install/AppManagerImpl.kt +++ b/app/src/main/java/foundation/e/apps/data/install/AppManagerImpl.kt @@ -33,6 +33,7 @@ import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PwaManager import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.installation.model.InstallationType +import foundation.e.apps.data.installation.repository.AppInstallRepository import foundation.e.apps.data.parentalcontrol.ContentRatingDao import foundation.e.apps.data.parentalcontrol.ContentRatingEntity import foundation.e.apps.domain.model.install.Status @@ -46,7 +47,6 @@ import javax.inject.Inject import javax.inject.Named import javax.inject.Singleton import com.aurora.gplayapi.data.models.PlayFile as AuroraFile - @Singleton class AppManagerImpl @Inject constructor( @Named("cacheDir") private val cacheDir: String, diff --git a/app/src/main/java/foundation/e/apps/data/install/core/InstallationProcessor.kt b/app/src/main/java/foundation/e/apps/data/install/core/InstallationProcessor.kt index 1809ab97c..1dee1a5e2 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/InstallationProcessor.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/InstallationProcessor.kt @@ -19,11 +19,11 @@ package foundation.e.apps.data.install.core import foundation.e.apps.data.enums.ResultStatus -import foundation.e.apps.data.install.AppInstallRepository 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.repository.AppInstallRepository import foundation.e.apps.domain.model.install.Status import kotlinx.coroutines.CancellationException import kotlinx.coroutines.DelicateCoroutinesApi diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/DownloadUrlRefresher.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/DownloadUrlRefresher.kt index c9443bd46..5a84f53b5 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/DownloadUrlRefresher.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/DownloadUrlRefresher.kt @@ -23,12 +23,12 @@ import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.application.ApplicationRepository import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.event.AppEvent -import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.toAppSource import foundation.e.apps.data.install.wrapper.AppEventDispatcher import foundation.e.apps.data.installation.model.AppInstall +import foundation.e.apps.data.installation.repository.AppInstallRepository import foundation.e.apps.data.playstore.utils.GplayHttpRequestException import kotlinx.coroutines.CancellationException import timber.log.Timber diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationCompletionHandler.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationCompletionHandler.kt index 6deca951d..0d1d2723a 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationCompletionHandler.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationCompletionHandler.kt @@ -21,11 +21,11 @@ package foundation.e.apps.data.install.core.helper import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R -import foundation.e.apps.data.install.AppInstallRepository 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.repository.AppInstallRepository import foundation.e.apps.data.preference.PlayStoreAuthStore import foundation.e.apps.data.utils.getFormattedString import foundation.e.apps.domain.model.install.Status diff --git a/app/src/main/java/foundation/e/apps/data/install/pkg/PwaManager.kt b/app/src/main/java/foundation/e/apps/data/install/pkg/PwaManager.kt index 8115341bd..4525f34a9 100644 --- a/app/src/main/java/foundation/e/apps/data/install/pkg/PwaManager.kt +++ b/app/src/main/java/foundation/e/apps/data/install/pkg/PwaManager.kt @@ -15,8 +15,8 @@ import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.OpenForTesting import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.install.AppInstallIconUrlBuilder -import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.installation.model.AppInstall +import foundation.e.apps.data.installation.repository.AppInstallRepository import foundation.e.apps.domain.model.install.Status import kotlinx.coroutines.delay import timber.log.Timber diff --git a/app/src/main/java/foundation/e/apps/data/install/receiver/PwaPlayerStatusReceiver.kt b/app/src/main/java/foundation/e/apps/data/install/receiver/PwaPlayerStatusReceiver.kt index 70a727361..e4ba9b786 100644 --- a/app/src/main/java/foundation/e/apps/data/install/receiver/PwaPlayerStatusReceiver.kt +++ b/app/src/main/java/foundation/e/apps/data/install/receiver/PwaPlayerStatusReceiver.kt @@ -22,7 +22,7 @@ import android.content.Context import android.content.Intent import dagger.hilt.android.AndroidEntryPoint import foundation.e.apps.data.di.qualifiers.IoCoroutineScope -import foundation.e.apps.data.install.AppInstallRepository +import foundation.e.apps.data.installation.repository.AppInstallRepository import foundation.e.apps.domain.model.install.Status import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallOrchestrator.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallOrchestrator.kt index 9828d8f8d..ab8117693 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallOrchestrator.kt +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallOrchestrator.kt @@ -25,8 +25,8 @@ import androidx.work.await import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.data.di.qualifiers.IoCoroutineScope import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.installation.local.AppInstallDAO import foundation.e.apps.data.installation.model.AppInstall +import foundation.e.apps.data.installation.repository.AppInstallRepository import foundation.e.apps.domain.model.install.Status import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope @@ -40,7 +40,7 @@ class InstallOrchestrator @Inject constructor( @param:ApplicationContext val context: Context, @param:IoCoroutineScope private val scope: CoroutineScope, private val appManagerWrapper: AppManagerWrapper, - private val installDao: AppInstallDAO + private val appInstallRepository: AppInstallRepository ) { fun init() { @@ -55,7 +55,7 @@ class InstallOrchestrator @Inject constructor( } private fun observeDownloads() { - installDao.getDownloads().onEach { list -> + appInstallRepository.getDownloads().onEach { list -> runCatching { if (list.none { it.status == Status.DOWNLOADING || it.status == Status.INSTALLING }) { list.find { it.status == Status.AWAITING } @@ -93,7 +93,7 @@ class InstallOrchestrator @Inject constructor( private suspend fun cancelFailedDownloads() { val workManager = WorkManager.getInstance(context) - val apps = installDao.getDownloads().firstOrNull().orEmpty() + val apps = appInstallRepository.getDownloads().firstOrNull().orEmpty() val activeWorkStates = setOf( WorkInfo.State.ENQUEUED, diff --git a/app/src/main/java/foundation/e/apps/data/receivers/DumpAppInstallStatusReceiver.kt b/app/src/main/java/foundation/e/apps/data/receivers/DumpAppInstallStatusReceiver.kt index 2593c3f7d..274951b0b 100644 --- a/app/src/main/java/foundation/e/apps/data/receivers/DumpAppInstallStatusReceiver.kt +++ b/app/src/main/java/foundation/e/apps/data/receivers/DumpAppInstallStatusReceiver.kt @@ -28,8 +28,8 @@ 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.install.AppInstallRepository import foundation.e.apps.data.installation.model.AppInstall +import foundation.e.apps.data.installation.repository.AppInstallRepository import foundation.e.apps.data.system.NetworkStatusManager import foundation.e.apps.data.system.StorageComputer import foundation.e.apps.domain.model.install.Status diff --git a/app/src/main/java/foundation/e/apps/domain/install/InstallAppByIdUseCase.kt b/app/src/main/java/foundation/e/apps/domain/install/InstallAppByIdUseCase.kt index ea3a11702..155e7a3fb 100644 --- a/app/src/main/java/foundation/e/apps/domain/install/InstallAppByIdUseCase.kt +++ b/app/src/main/java/foundation/e/apps/domain/install/InstallAppByIdUseCase.kt @@ -20,10 +20,10 @@ package foundation.e.apps.domain.install import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.install.AppInstallRepository import foundation.e.apps.data.install.core.AppInstallationFacade import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PwaManager +import foundation.e.apps.data.installation.repository.AppInstallRepository import foundation.e.apps.domain.model.install.Status import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow diff --git a/app/src/test/java/foundation/e/apps/domain/install/InstallAppByIdUseCaseTest.kt b/app/src/test/java/foundation/e/apps/domain/install/InstallAppByIdUseCaseTest.kt index 002aa2b0c..0224d9d44 100644 --- a/app/src/test/java/foundation/e/apps/domain/install/InstallAppByIdUseCaseTest.kt +++ b/app/src/test/java/foundation/e/apps/domain/install/InstallAppByIdUseCaseTest.kt @@ -20,7 +20,7 @@ package foundation.e.apps.domain.install import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.install.AppInstallRepository +import foundation.e.apps.data.installation.repository.AppInstallRepository import foundation.e.apps.data.install.core.AppInstallationFacade import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.install.pkg.AppLoungePackageManager diff --git a/app/src/test/java/foundation/e/apps/fusedManager/FakeAppManager.kt b/app/src/test/java/foundation/e/apps/fusedManager/FakeAppManager.kt index c1084c20f..e76afd006 100644 --- a/app/src/test/java/foundation/e/apps/fusedManager/FakeAppManager.kt +++ b/app/src/test/java/foundation/e/apps/fusedManager/FakeAppManager.kt @@ -19,10 +19,10 @@ package foundation.e.apps.fusedManager import androidx.lifecycle.LiveData -import foundation.e.apps.domain.model.install.Status -import foundation.e.apps.data.installation.local.AppInstallDAO import foundation.e.apps.data.install.AppManager +import foundation.e.apps.data.installation.local.AppInstallDAO import foundation.e.apps.data.installation.model.AppInstall +import foundation.e.apps.domain.model.install.Status import java.io.File class FakeAppManager(private val appInstallDAO: AppInstallDAO) : AppManager { diff --git a/app/src/test/java/foundation/e/apps/install/workmanager/InstallOrchestratorTest.kt b/app/src/test/java/foundation/e/apps/install/workmanager/InstallOrchestratorTest.kt index abd0ee297..cb28f4b0e 100644 --- a/app/src/test/java/foundation/e/apps/install/workmanager/InstallOrchestratorTest.kt +++ b/app/src/test/java/foundation/e/apps/install/workmanager/InstallOrchestratorTest.kt @@ -31,9 +31,9 @@ import androidx.work.WorkManager import androidx.work.testing.WorkManagerTestInitHelper import androidx.test.core.app.ApplicationProvider import com.google.common.util.concurrent.Futures -import foundation.e.apps.data.installation.local.AppInstallDAO import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.installation.model.AppInstall +import foundation.e.apps.data.installation.repository.AppInstallRepository import foundation.e.apps.data.install.workmanager.InstallOrchestrator import foundation.e.apps.data.install.workmanager.InstallWorkManager import foundation.e.apps.domain.model.install.Status @@ -73,7 +73,7 @@ class InstallOrchestratorTest { val mainCoroutineRule = MainCoroutineRule() private val context: Context = ApplicationProvider.getApplicationContext() - private val installDao = mock() + private val appInstallRepository = mock() private val appManagerWrapper = mock() private var isInstallWorkManagerMocked = false @@ -95,10 +95,10 @@ class InstallOrchestratorTest { fun init_marksStaleDownloadAsInstallationIssue() = runTest { val app = createAppInstall(status = Status.DOWNLOADING) - whenever(installDao.getDownloads()).thenReturn(flowOf(listOf(app)), emptyFlow()) + whenever(appInstallRepository.getDownloads()).thenReturn(flowOf(listOf(app)), emptyFlow()) whenever(appManagerWrapper.isFusedDownloadInstalled(app)).thenReturn(false) - val installOrchestrator = InstallOrchestrator(context, this, appManagerWrapper, installDao) + val installOrchestrator = InstallOrchestrator(context, this, appManagerWrapper, appInstallRepository) installOrchestrator.init() advanceUntilIdle() @@ -111,10 +111,10 @@ class InstallOrchestratorTest { fun init_updatesStatusToInstalledWhenAppAlreadyInstalled() = runTest { val app = createAppInstall(status = Status.DOWNLOADED) - whenever(installDao.getDownloads()).thenReturn(flowOf(listOf(app)), emptyFlow()) + whenever(appInstallRepository.getDownloads()).thenReturn(flowOf(listOf(app)), emptyFlow()) whenever(appManagerWrapper.isFusedDownloadInstalled(app)).thenReturn(true) - val installOrchestrator = InstallOrchestrator(context, this, appManagerWrapper, installDao) + val installOrchestrator = InstallOrchestrator(context, this, appManagerWrapper, appInstallRepository) installOrchestrator.init() advanceUntilIdle() @@ -137,9 +137,9 @@ class InstallOrchestratorTest { whenever(sessionInfo.isActive).thenReturn(true) whenever(packageManager.packageInstaller).thenReturn(packageInstaller) whenever(packageInstaller.allSessions).thenReturn(listOf(sessionInfo)) - whenever(installDao.getDownloads()).thenReturn(flowOf(listOf(app)), emptyFlow()) + whenever(appInstallRepository.getDownloads()).thenReturn(flowOf(listOf(app)), emptyFlow()) - val installOrchestrator = InstallOrchestrator(wrappedContext, this, appManagerWrapper, installDao) + val installOrchestrator = InstallOrchestrator(wrappedContext, this, appManagerWrapper, appInstallRepository) installOrchestrator.init() advanceUntilIdle() @@ -153,9 +153,9 @@ class InstallOrchestratorTest { val awaiting = createAppInstall(id = "app.awaiting", status = Status.AWAITING) mockInstallWorkManagerSuccess() - whenever(installDao.getDownloads()).thenReturn(flowOf(emptyList()), flowOf(listOf(awaiting))) + whenever(appInstallRepository.getDownloads()).thenReturn(flowOf(emptyList()), flowOf(listOf(awaiting))) - val installOrchestrator = InstallOrchestrator(context, this, appManagerWrapper, installDao) + val installOrchestrator = InstallOrchestrator(context, this, appManagerWrapper, appInstallRepository) installOrchestrator.init() advanceUntilIdle() @@ -169,10 +169,10 @@ class InstallOrchestratorTest { val awaiting = createAppInstall(id = "app.awaiting", status = Status.AWAITING) mockInstallWorkManagerSuccess() - whenever(installDao.getDownloads()) + whenever(appInstallRepository.getDownloads()) .thenReturn(flowOf(emptyList()), flowOf(listOf(active, awaiting))) - val installOrchestrator = InstallOrchestrator(context, this, appManagerWrapper, installDao) + val installOrchestrator = InstallOrchestrator(context, this, appManagerWrapper, appInstallRepository) installOrchestrator.init() advanceUntilIdle() @@ -185,9 +185,9 @@ class InstallOrchestratorTest { val awaiting = createAppInstall(id = "app.awaiting", status = Status.AWAITING) mockInstallWorkManagerFailure() - whenever(installDao.getDownloads()).thenReturn(flowOf(emptyList()), flowOf(listOf(awaiting))) + whenever(appInstallRepository.getDownloads()).thenReturn(flowOf(emptyList()), flowOf(listOf(awaiting))) - val installOrchestrator = InstallOrchestrator(context, this, appManagerWrapper, installDao) + val installOrchestrator = InstallOrchestrator(context, this, appManagerWrapper, appInstallRepository) installOrchestrator.init() advanceUntilIdle() @@ -200,9 +200,9 @@ class InstallOrchestratorTest { val awaiting = createAppInstall(id = "app.awaiting.cancelled", status = Status.AWAITING) mockInstallWorkManagerCancellation() - whenever(installDao.getDownloads()).thenReturn(flowOf(emptyList()), flowOf(listOf(awaiting))) + whenever(appInstallRepository.getDownloads()).thenReturn(flowOf(emptyList()), flowOf(listOf(awaiting))) - val installOrchestrator = InstallOrchestrator(context, this, appManagerWrapper, installDao) + val installOrchestrator = InstallOrchestrator(context, this, appManagerWrapper, appInstallRepository) installOrchestrator.init() advanceUntilIdle() @@ -219,9 +219,9 @@ class InstallOrchestratorTest { .build() WorkManager.getInstance(context).enqueue(request).result.get() - whenever(installDao.getDownloads()).thenReturn(flowOf(listOf(app)), emptyFlow()) + whenever(appInstallRepository.getDownloads()).thenReturn(flowOf(listOf(app)), emptyFlow()) - val installOrchestrator = InstallOrchestrator(context, this, appManagerWrapper, installDao) + val installOrchestrator = InstallOrchestrator(context, this, appManagerWrapper, appInstallRepository) installOrchestrator.init() advanceUntilIdle() @@ -236,9 +236,9 @@ class InstallOrchestratorTest { val awaiting = createAppInstall(id = "app.awaiting.success", status = Status.AWAITING) mockInstallWorkManagerSuccess() - whenever(installDao.getDownloads()).thenReturn(flowOf(emptyList()), flowOf(listOf(awaiting))) + whenever(appInstallRepository.getDownloads()).thenReturn(flowOf(emptyList()), flowOf(listOf(awaiting))) - val installOrchestrator = InstallOrchestrator(context, this, appManagerWrapper, installDao) + val installOrchestrator = InstallOrchestrator(context, this, appManagerWrapper, appInstallRepository) installOrchestrator.init() advanceUntilIdle() @@ -251,11 +251,11 @@ class InstallOrchestratorTest { val awaiting = createAppInstall(id = "app.awaiting.after.exception", status = Status.AWAITING) mockInstallWorkManagerSuccess() - whenever(installDao.getDownloads()) + whenever(appInstallRepository.getDownloads()) .thenThrow(RuntimeException("reconcile failed")) .thenReturn(flowOf(listOf(awaiting))) - val installOrchestrator = InstallOrchestrator(context, this, appManagerWrapper, installDao) + val installOrchestrator = InstallOrchestrator(context, this, appManagerWrapper, appInstallRepository) installOrchestrator.init() advanceUntilIdle() @@ -265,14 +265,14 @@ class InstallOrchestratorTest { @Test fun init_stopsAfterCancellationExceptionDuringReconciliation() = runTest { - whenever(installDao.getDownloads()).thenThrow(CancellationException("cancel reconcile")) + whenever(appInstallRepository.getDownloads()).thenThrow(CancellationException("cancel reconcile")) - val installOrchestrator = InstallOrchestrator(context, this, appManagerWrapper, installDao) + val installOrchestrator = InstallOrchestrator(context, this, appManagerWrapper, appInstallRepository) installOrchestrator.init() advanceUntilIdle() - verifyMockito(installDao, times(1)).getDownloads() + verifyMockito(appInstallRepository, times(1)).getDownloads() } private fun mockInstallWorkManagerSuccess() { diff --git a/app/src/test/java/foundation/e/apps/installProcessor/DownloadUrlRefresherTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/DownloadUrlRefresherTest.kt index 61264274e..47861c953 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/DownloadUrlRefresherTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/DownloadUrlRefresherTest.kt @@ -22,7 +22,7 @@ import com.aurora.gplayapi.exceptions.InternalException import foundation.e.apps.data.enums.Source import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.application.ApplicationRepository -import foundation.e.apps.data.install.AppInstallRepository +import foundation.e.apps.data.installation.repository.AppInstallRepository import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.installation.model.AppInstall diff --git a/app/src/test/java/foundation/e/apps/installProcessor/FakeAppInstallDAO.kt b/app/src/test/java/foundation/e/apps/installProcessor/FakeAppInstallDAO.kt index 3385dc2fa..b8cd526fd 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/FakeAppInstallDAO.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/FakeAppInstallDAO.kt @@ -20,9 +20,9 @@ package foundation.e.apps.installProcessor import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData -import foundation.e.apps.domain.model.install.Status import foundation.e.apps.data.installation.local.AppInstallDAO import foundation.e.apps.data.installation.model.AppInstall +import foundation.e.apps.domain.model.install.Status import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flow diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallationCompletionHandlerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationCompletionHandlerTest.kt index d6f7955d0..12db88fdf 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/InstallationCompletionHandlerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationCompletionHandlerTest.kt @@ -21,7 +21,7 @@ package foundation.e.apps.installProcessor import android.content.Context import com.aurora.gplayapi.data.models.AuthData import foundation.e.apps.R -import foundation.e.apps.data.install.AppInstallRepository +import foundation.e.apps.data.installation.repository.AppInstallRepository import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.install.core.helper.InstallationCompletionHandler diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt index 1abda6c7a..5fd40677e 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt @@ -28,7 +28,7 @@ import foundation.e.apps.data.application.ApplicationRepository import foundation.e.apps.data.enums.Source import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppManager -import foundation.e.apps.data.install.AppInstallRepository +import foundation.e.apps.data.installation.repository.AppInstallRepository import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt index cbe892f16..a97c31bce 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt @@ -21,7 +21,7 @@ package foundation.e.apps.installProcessor import android.content.Context import androidx.arch.core.executor.testing.InstantTaskExecutorRule import foundation.e.apps.data.fdroid.FDroidRepository -import foundation.e.apps.data.install.AppInstallRepository +import foundation.e.apps.data.installation.repository.AppInstallRepository import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.download.DownloadManagerUtils import foundation.e.apps.data.installation.model.AppInstall diff --git a/app/src/main/java/foundation/e/apps/data/install/AppInstallRepository.kt b/data/src/main/java/foundation/e/apps/data/installation/repository/AppInstallRepository.kt similarity index 82% rename from app/src/main/java/foundation/e/apps/data/install/AppInstallRepository.kt rename to data/src/main/java/foundation/e/apps/data/installation/repository/AppInstallRepository.kt index 6e814fddb..56fcf3bad 100644 --- a/app/src/main/java/foundation/e/apps/data/install/AppInstallRepository.kt +++ b/data/src/main/java/foundation/e/apps/data/installation/repository/AppInstallRepository.kt @@ -1,8 +1,7 @@ -package foundation.e.apps.data.install +package foundation.e.apps.data.installation.repository import androidx.lifecycle.LiveData import androidx.lifecycle.asFlow -import foundation.e.apps.OpenForTesting import foundation.e.apps.data.installation.local.AppInstallDAO import foundation.e.apps.data.installation.model.AppInstall import kotlinx.coroutines.flow.Flow @@ -12,7 +11,6 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -@OpenForTesting class AppInstallRepository @Inject constructor( private val appInstallDAO: AppInstallDAO ) { @@ -35,6 +33,16 @@ class AppInstallRepository @Inject constructor( return appInstallDAO.getDownloadLiveList() } + fun getDownloads(): Flow> { + return appInstallDAO.getDownloads() + } + + suspend fun getItemInInstallation(): List { + mutex.withLock { + return appInstallDAO.getItemInInstallation() + } + } + suspend fun updateDownload(appInstall: AppInstall) { mutex.withLock { appInstallDAO.updateDownload(appInstall) -- GitLab From 19d33e70da87a53728dde451242c8da78e817121 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Mon, 30 Mar 2026 15:53:36 +0600 Subject: [PATCH 40/43] refactor(install): move install persistence DI to data module --- .../e/apps/data/di/db/DatabaseModule.kt | 26 ------------------ .../di/AppInstallPersistenceModule.kt | 27 +++++++++++++++++++ 2 files changed, 27 insertions(+), 26 deletions(-) create mode 100644 data/src/main/java/foundation/e/apps/data/installation/di/AppInstallPersistenceModule.kt diff --git a/app/src/main/java/foundation/e/apps/data/di/db/DatabaseModule.kt b/app/src/main/java/foundation/e/apps/data/di/db/DatabaseModule.kt index 3dd224793..0404f691d 100644 --- a/app/src/main/java/foundation/e/apps/data/di/db/DatabaseModule.kt +++ b/app/src/main/java/foundation/e/apps/data/di/db/DatabaseModule.kt @@ -1,27 +1 @@ package foundation.e.apps.data.di.db - -import android.content.Context -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import foundation.e.apps.data.installation.local.AppInstallDAO -import foundation.e.apps.data.installation.local.AppInstallDatabase -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object DatabaseModule { - @Singleton - @Provides - fun provideDatabaseInstance(@ApplicationContext context: Context): AppInstallDatabase { - return AppInstallDatabase.getInstance(context) - } - - @Singleton - @Provides - fun provideFusedDaoInstance(appInstallDatabase: AppInstallDatabase): AppInstallDAO { - return appInstallDatabase.fusedDownloadDao() - } -} diff --git a/data/src/main/java/foundation/e/apps/data/installation/di/AppInstallPersistenceModule.kt b/data/src/main/java/foundation/e/apps/data/installation/di/AppInstallPersistenceModule.kt new file mode 100644 index 000000000..1fbf62d44 --- /dev/null +++ b/data/src/main/java/foundation/e/apps/data/installation/di/AppInstallPersistenceModule.kt @@ -0,0 +1,27 @@ +package foundation.e.apps.data.installation.di + +import android.content.Context +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import foundation.e.apps.data.installation.local.AppInstallDAO +import foundation.e.apps.data.installation.local.AppInstallDatabase +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object AppInstallPersistenceModule { + @Singleton + @Provides + fun provideDatabaseInstance(@ApplicationContext context: Context): AppInstallDatabase { + return AppInstallDatabase.getInstance(context) + } + + @Singleton + @Provides + fun provideFusedDaoInstance(appInstallDatabase: AppInstallDatabase): AppInstallDAO { + return appInstallDatabase.fusedDownloadDao() + } +} -- GitLab From 684e086b2dda1be7b709ecd9ba04fc98b48de1a2 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Mon, 30 Mar 2026 16:01:29 +0600 Subject: [PATCH 41/43] refactor(install): introduce data-owned ports for installation processing --- .../data/di/bindings/AppInstallationModule.kt | 20 +++++++++++ .../e/apps/data/install/AppManagerWrapper.kt | 18 ++++++---- .../install/core/InstallationProcessor.kt | 35 ++++++++++--------- .../helper/InstallationCompletionHandler.kt | 5 +-- .../install/download/DownloadManagerUtils.kt | 5 +-- .../port/InstallationAppManager.kt | 18 ++++++++++ .../port/InstallationCompletionNotifier.kt | 7 ++++ .../port/InstallationDownloadStatusUpdater.kt | 5 +++ 8 files changed, 86 insertions(+), 27 deletions(-) create mode 100644 data/src/main/java/foundation/e/apps/data/installation/port/InstallationAppManager.kt create mode 100644 data/src/main/java/foundation/e/apps/data/installation/port/InstallationCompletionNotifier.kt create mode 100644 data/src/main/java/foundation/e/apps/data/installation/port/InstallationDownloadStatusUpdater.kt diff --git a/app/src/main/java/foundation/e/apps/data/di/bindings/AppInstallationModule.kt b/app/src/main/java/foundation/e/apps/data/di/bindings/AppInstallationModule.kt index d4e7cbf8a..81b22490a 100644 --- a/app/src/main/java/foundation/e/apps/data/di/bindings/AppInstallationModule.kt +++ b/app/src/main/java/foundation/e/apps/data/di/bindings/AppInstallationModule.kt @@ -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 @@ -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 @@ -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 } diff --git a/app/src/main/java/foundation/e/apps/data/install/AppManagerWrapper.kt b/app/src/main/java/foundation/e/apps/data/install/AppManagerWrapper.kt index fab2df46f..27b6c9f3e 100644 --- a/app/src/main/java/foundation/e/apps/data/install/AppManagerWrapper.kt +++ b/app/src/main/java/foundation/e/apps/data/install/AppManagerWrapper.kt @@ -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) } @@ -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) } @@ -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( @@ -212,7 +218,7 @@ class AppManagerWrapper @Inject constructor( ) } - fun isFusedDownloadInstalled(appInstall: AppInstall): Boolean { + override fun isFusedDownloadInstalled(appInstall: AppInstall): Boolean { return appManager.isAppInstalled(appInstall) } diff --git a/app/src/main/java/foundation/e/apps/data/install/core/InstallationProcessor.kt b/app/src/main/java/foundation/e/apps/data/install/core/InstallationProcessor.kt index 1dee1a5e2..7ab5c5141 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/InstallationProcessor.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/InstallationProcessor.kt @@ -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 @@ -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) @@ -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) @@ -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) @@ -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) } } @@ -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}") } @@ -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) } @@ -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 ) @@ -186,6 +187,6 @@ class InstallationProcessor @Inject constructor( } private suspend fun finishInstallation(appInstall: AppInstall, isUpdateWork: Boolean) { - installationCompletionHandler.onInstallFinished(appInstall, isUpdateWork) + installationCompletionNotifier.onInstallFinished(appInstall, isUpdateWork) } } diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationCompletionHandler.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationCompletionHandler.kt index 0d1d2723a..a5eff79da 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationCompletionHandler.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationCompletionHandler.kt @@ -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 @@ -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 } diff --git a/app/src/main/java/foundation/e/apps/data/install/download/DownloadManagerUtils.kt b/app/src/main/java/foundation/e/apps/data/install/download/DownloadManagerUtils.kt index 6fa8541ff..8a2f6f54d 100644 --- a/app/src/main/java/foundation/e/apps/data/install/download/DownloadManagerUtils.kt +++ b/app/src/main/java/foundation/e/apps/data/install/download/DownloadManagerUtils.kt @@ -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 @@ -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 @@ -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 diff --git a/data/src/main/java/foundation/e/apps/data/installation/port/InstallationAppManager.kt b/data/src/main/java/foundation/e/apps/data/installation/port/InstallationAppManager.kt new file mode 100644 index 000000000..2503e5415 --- /dev/null +++ b/data/src/main/java/foundation/e/apps/data/installation/port/InstallationAppManager.kt @@ -0,0 +1,18 @@ +package foundation.e.apps.data.installation.port + +import foundation.e.apps.data.installation.model.AppInstall +import foundation.e.apps.domain.model.install.Status + +interface InstallationAppManager { + fun validateFusedDownload(appInstall: AppInstall): Boolean + + suspend fun installationIssue(appInstall: AppInstall) + + fun isFusedDownloadInstalled(appInstall: AppInstall): Boolean + + suspend fun updateDownloadStatus(appInstall: AppInstall, status: Status) + + suspend fun cancelDownload(appInstall: AppInstall) + + suspend fun downloadApp(appInstall: AppInstall, isUpdate: Boolean) +} diff --git a/data/src/main/java/foundation/e/apps/data/installation/port/InstallationCompletionNotifier.kt b/data/src/main/java/foundation/e/apps/data/installation/port/InstallationCompletionNotifier.kt new file mode 100644 index 000000000..1a16b58e9 --- /dev/null +++ b/data/src/main/java/foundation/e/apps/data/installation/port/InstallationCompletionNotifier.kt @@ -0,0 +1,7 @@ +package foundation.e.apps.data.installation.port + +import foundation.e.apps.data.installation.model.AppInstall + +interface InstallationCompletionNotifier { + suspend fun onInstallFinished(appInstall: AppInstall?, isUpdateWork: Boolean) +} diff --git a/data/src/main/java/foundation/e/apps/data/installation/port/InstallationDownloadStatusUpdater.kt b/data/src/main/java/foundation/e/apps/data/installation/port/InstallationDownloadStatusUpdater.kt new file mode 100644 index 000000000..bd3e8865f --- /dev/null +++ b/data/src/main/java/foundation/e/apps/data/installation/port/InstallationDownloadStatusUpdater.kt @@ -0,0 +1,5 @@ +package foundation.e.apps.data.installation.port + +interface InstallationDownloadStatusUpdater { + fun updateDownloadStatus(downloadId: Long) +} -- GitLab From c9e8b21cb9319246bbac911f89cf2696fb405d19 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Mon, 30 Mar 2026 16:08:45 +0600 Subject: [PATCH 42/43] refactor(install): move InstallationProcessor to data module --- .../install/core/AppInstallationFacade.kt | 10 +++++++ .../AppInstallationFacadeTest.kt | 5 ++-- .../InstallationProcessorTest.kt | 2 +- data/build.gradle | 1 + .../core/InstallationProcessor.kt | 28 ++++--------------- .../installation/model/InstallationResult.kt | 10 +++++++ 6 files changed, 30 insertions(+), 26 deletions(-) rename {app/src/main/java/foundation/e/apps/data/install => data/src/main/java/foundation/e/apps/data/installation}/core/InstallationProcessor.kt (88%) create mode 100644 data/src/main/java/foundation/e/apps/data/installation/model/InstallationResult.kt diff --git a/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt b/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt index 58af0dd2a..8f6c329fa 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/AppInstallationFacade.kt @@ -21,7 +21,9 @@ package foundation.e.apps.data.install.core import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.install.AppManagerWrapper +import foundation.e.apps.data.installation.core.InstallationProcessor import foundation.e.apps.data.installation.model.AppInstall +import foundation.e.apps.data.installation.model.InstallationResult import foundation.e.apps.domain.model.install.Status import javax.inject.Inject @@ -71,5 +73,13 @@ class AppInstallationFacade @Inject constructor( runInForeground: (suspend (String) -> Unit) ): Result { return installationProcessor.processInstall(fusedDownloadId, isItUpdateWork, runInForeground) + .map { installationResult -> + when (installationResult) { + InstallationResult.OK -> ResultStatus.OK + InstallationResult.TIMEOUT -> ResultStatus.TIMEOUT + InstallationResult.UNKNOWN -> ResultStatus.UNKNOWN + InstallationResult.RETRY -> ResultStatus.RETRY + } + } } } diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt index 4ac0061ea..6f6af2a56 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallationFacadeTest.kt @@ -26,9 +26,10 @@ import foundation.e.apps.data.enums.Type import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.core.AppInstallationFacade import foundation.e.apps.data.install.core.InstallationEnqueuer -import foundation.e.apps.data.install.core.InstallationProcessor import foundation.e.apps.data.install.core.InstallationRequest import foundation.e.apps.data.installation.model.AppInstall +import foundation.e.apps.data.installation.model.InstallationResult +import foundation.e.apps.data.installation.core.InstallationProcessor import foundation.e.apps.domain.model.install.Status import foundation.e.apps.util.MainCoroutineRule import io.mockk.coEvery @@ -115,7 +116,7 @@ class AppInstallationFacadeTest { fun processInstall_delegatesResult() = runTest { coEvery { installationProcessor.processInstall("123", false, any()) - } returns Result.success(ResultStatus.OK) + } returns Result.success(InstallationResult.OK) val result = appInstallationFacade.processInstall("123", false) { // _ignored_ diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt index a97c31bce..c737deb47 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationProcessorTest.kt @@ -25,7 +25,7 @@ import foundation.e.apps.data.installation.repository.AppInstallRepository import foundation.e.apps.data.install.AppManager import foundation.e.apps.data.install.download.DownloadManagerUtils import foundation.e.apps.data.installation.model.AppInstall -import foundation.e.apps.data.install.core.InstallationProcessor +import foundation.e.apps.data.installation.core.InstallationProcessor import foundation.e.apps.data.install.core.helper.InstallationCompletionHandler import foundation.e.apps.domain.model.install.Status import foundation.e.apps.util.MainCoroutineRule diff --git a/data/build.gradle b/data/build.gradle index 6699f24c0..95588f252 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -58,6 +58,7 @@ dependencies { implementation libs.lifecycle.livedata.ktx implementation libs.room.ktx implementation libs.room.runtime + implementation libs.timber ksp libs.room.compiler ksp libs.hilt.compile diff --git a/app/src/main/java/foundation/e/apps/data/install/core/InstallationProcessor.kt b/data/src/main/java/foundation/e/apps/data/installation/core/InstallationProcessor.kt similarity index 88% rename from app/src/main/java/foundation/e/apps/data/install/core/InstallationProcessor.kt rename to data/src/main/java/foundation/e/apps/data/installation/core/InstallationProcessor.kt index 7ab5c5141..33b70515b 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/InstallationProcessor.kt +++ b/data/src/main/java/foundation/e/apps/data/installation/core/InstallationProcessor.kt @@ -1,25 +1,7 @@ -/* - * Copyright (C) 2026 e Foundation - * - * 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.data.install.core - -import foundation.e.apps.data.enums.ResultStatus +package foundation.e.apps.data.installation.core + import foundation.e.apps.data.installation.model.AppInstall +import foundation.e.apps.data.installation.model.InstallationResult import foundation.e.apps.data.installation.port.InstallationAppManager import foundation.e.apps.data.installation.port.InstallationCompletionNotifier import foundation.e.apps.data.installation.port.InstallationDownloadStatusUpdater @@ -43,7 +25,7 @@ class InstallationProcessor @Inject constructor( fusedDownloadId: String, isItUpdateWork: Boolean, runInForeground: suspend (String) -> Unit - ): Result { + ): Result { val appInstall = appInstallRepository.getDownloadById(fusedDownloadId) ?: return Result.failure( IllegalStateException("App can't be null here.") @@ -81,7 +63,7 @@ class InstallationProcessor @Inject constructor( startAppInstallationProcess(appInstall, isUpdateWork) Timber.i("doWork: RESULT SUCCESS: ${appInstall.name}") - ResultStatus.OK + InstallationResult.OK }.onFailure { exception -> if (exception is CancellationException) { throw exception diff --git a/data/src/main/java/foundation/e/apps/data/installation/model/InstallationResult.kt b/data/src/main/java/foundation/e/apps/data/installation/model/InstallationResult.kt new file mode 100644 index 000000000..1a850266c --- /dev/null +++ b/data/src/main/java/foundation/e/apps/data/installation/model/InstallationResult.kt @@ -0,0 +1,10 @@ +package foundation.e.apps.data.installation.model + +enum class InstallationResult { + OK, + TIMEOUT, + UNKNOWN, + RETRY; + + var message: String = "" +} -- GitLab From 4ec03595fc18a1c798fe79c7616ee8510cc4c597 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Mon, 30 Mar 2026 16:23:53 +0600 Subject: [PATCH 43/43] refactor(install): move installation ports to data and keep adapters in app --- .../apps/data/di/bindings/AppInstallationModule.kt | 10 +++++----- .../e/apps/data/install/core/helper/AgeLimiter.kt | 2 +- .../data/install/core/helper/DevicePreconditions.kt | 4 ++-- .../core/helper/InstallationCompletionHandler.kt | 4 ++-- ...atusChecker.kt => DeviceNetworkStatusChecker.kt} | 5 +---- ...Gateway.kt => ParentalControlAuthGatewayImpl.kt} | 5 +---- ...geSpaceChecker.kt => StorageSpaceCheckerImpl.kt} | 5 +---- ...onSender.kt => UpdatesNotificationSenderImpl.kt} | 5 +---- .../{UpdatesTracker.kt => UpdatesTrackerImpl.kt} | 11 +---------- .../e/apps/installProcessor/AgeLimiterTest.kt | 2 +- .../installProcessor/DevicePreconditionsTest.kt | 4 ++-- .../InstallationCompletionHandlerTest.kt | 4 ++-- .../installProcessor/InstallationEnqueuerTest.kt | 4 ++-- .../data/installation/port/NetworkStatusChecker.kt | 5 +++++ .../installation/port/ParentalControlAuthGateway.kt | 5 +++++ .../data/installation/port/StorageSpaceChecker.kt | 7 +++++++ .../installation/port/UpdatesNotificationSender.kt | 5 +++++ .../e/apps/data/installation/port/UpdatesTracker.kt | 13 +++++++++++++ 18 files changed, 57 insertions(+), 43 deletions(-) rename app/src/main/java/foundation/e/apps/data/install/wrapper/{NetworkStatusChecker.kt => DeviceNetworkStatusChecker.kt} (93%) rename app/src/main/java/foundation/e/apps/data/install/wrapper/{ParentalControlAuthGateway.kt => ParentalControlAuthGatewayImpl.kt} (92%) rename app/src/main/java/foundation/e/apps/data/install/wrapper/{StorageSpaceChecker.kt => StorageSpaceCheckerImpl.kt} (92%) rename app/src/main/java/foundation/e/apps/data/install/wrapper/{UpdatesNotificationSender.kt => UpdatesNotificationSenderImpl.kt} (92%) rename app/src/main/java/foundation/e/apps/data/install/wrapper/{UpdatesTracker.kt => UpdatesTrackerImpl.kt} (86%) create mode 100644 data/src/main/java/foundation/e/apps/data/installation/port/NetworkStatusChecker.kt create mode 100644 data/src/main/java/foundation/e/apps/data/installation/port/ParentalControlAuthGateway.kt create mode 100644 data/src/main/java/foundation/e/apps/data/installation/port/StorageSpaceChecker.kt create mode 100644 data/src/main/java/foundation/e/apps/data/installation/port/UpdatesNotificationSender.kt create mode 100644 data/src/main/java/foundation/e/apps/data/installation/port/UpdatesTracker.kt diff --git a/app/src/main/java/foundation/e/apps/data/di/bindings/AppInstallationModule.kt b/app/src/main/java/foundation/e/apps/data/di/bindings/AppInstallationModule.kt index 81b22490a..8d95af38e 100644 --- a/app/src/main/java/foundation/e/apps/data/di/bindings/AppInstallationModule.kt +++ b/app/src/main/java/foundation/e/apps/data/di/bindings/AppInstallationModule.kt @@ -28,18 +28,18 @@ 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 -import foundation.e.apps.data.install.wrapper.NetworkStatusChecker -import foundation.e.apps.data.install.wrapper.ParentalControlAuthGateway import foundation.e.apps.data.install.wrapper.ParentalControlAuthGatewayImpl -import foundation.e.apps.data.install.wrapper.StorageSpaceChecker import foundation.e.apps.data.install.wrapper.StorageSpaceCheckerImpl -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 foundation.e.apps.data.installation.port.NetworkStatusChecker +import foundation.e.apps.data.installation.port.ParentalControlAuthGateway +import foundation.e.apps.data.installation.port.StorageSpaceChecker +import foundation.e.apps.data.installation.port.UpdatesNotificationSender +import foundation.e.apps.data.installation.port.UpdatesTracker import javax.inject.Singleton @Module diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/AgeLimiter.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/AgeLimiter.kt index ee17e8162..909bcdba1 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/AgeLimiter.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/AgeLimiter.kt @@ -23,8 +23,8 @@ import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.wrapper.AppEventDispatcher -import foundation.e.apps.data.install.wrapper.ParentalControlAuthGateway import foundation.e.apps.data.installation.model.AppInstall +import foundation.e.apps.data.installation.port.ParentalControlAuthGateway import foundation.e.apps.domain.ValidateAppAgeLimitUseCase import foundation.e.apps.domain.model.ContentRatingValidity import kotlinx.coroutines.CompletableDeferred diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/DevicePreconditions.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/DevicePreconditions.kt index 6fd979dad..2a6c92347 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/DevicePreconditions.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/DevicePreconditions.kt @@ -23,9 +23,9 @@ import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.notification.StorageNotificationManager import foundation.e.apps.data.install.wrapper.AppEventDispatcher -import foundation.e.apps.data.install.wrapper.NetworkStatusChecker -import foundation.e.apps.data.install.wrapper.StorageSpaceChecker import foundation.e.apps.data.installation.model.AppInstall +import foundation.e.apps.data.installation.port.NetworkStatusChecker +import foundation.e.apps.data.installation.port.StorageSpaceChecker import timber.log.Timber import javax.inject.Inject class DevicePreconditions @Inject constructor( diff --git a/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationCompletionHandler.kt b/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationCompletionHandler.kt index a5eff79da..e1c421217 100644 --- a/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationCompletionHandler.kt +++ b/app/src/main/java/foundation/e/apps/data/install/core/helper/InstallationCompletionHandler.kt @@ -22,10 +22,10 @@ import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R 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.port.UpdatesNotificationSender +import foundation.e.apps.data.installation.port.UpdatesTracker import foundation.e.apps.data.installation.repository.AppInstallRepository import foundation.e.apps.data.preference.PlayStoreAuthStore import foundation.e.apps.data.utils.getFormattedString diff --git a/app/src/main/java/foundation/e/apps/data/install/wrapper/NetworkStatusChecker.kt b/app/src/main/java/foundation/e/apps/data/install/wrapper/DeviceNetworkStatusChecker.kt similarity index 93% rename from app/src/main/java/foundation/e/apps/data/install/wrapper/NetworkStatusChecker.kt rename to app/src/main/java/foundation/e/apps/data/install/wrapper/DeviceNetworkStatusChecker.kt index 612ed2b0c..f278602d2 100644 --- a/app/src/main/java/foundation/e/apps/data/install/wrapper/NetworkStatusChecker.kt +++ b/app/src/main/java/foundation/e/apps/data/install/wrapper/DeviceNetworkStatusChecker.kt @@ -20,13 +20,10 @@ package foundation.e.apps.data.install.wrapper import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.apps.data.installation.port.NetworkStatusChecker import foundation.e.apps.data.system.isNetworkAvailable import javax.inject.Inject -interface NetworkStatusChecker { - fun isNetworkAvailable(): Boolean -} - class DeviceNetworkStatusChecker @Inject constructor( @ApplicationContext private val context: Context ) : NetworkStatusChecker { diff --git a/app/src/main/java/foundation/e/apps/data/install/wrapper/ParentalControlAuthGateway.kt b/app/src/main/java/foundation/e/apps/data/install/wrapper/ParentalControlAuthGatewayImpl.kt similarity index 92% rename from app/src/main/java/foundation/e/apps/data/install/wrapper/ParentalControlAuthGateway.kt rename to app/src/main/java/foundation/e/apps/data/install/wrapper/ParentalControlAuthGatewayImpl.kt index 36829868b..864d9c13b 100644 --- a/app/src/main/java/foundation/e/apps/data/install/wrapper/ParentalControlAuthGateway.kt +++ b/app/src/main/java/foundation/e/apps/data/install/wrapper/ParentalControlAuthGatewayImpl.kt @@ -18,13 +18,10 @@ package foundation.e.apps.data.install.wrapper +import foundation.e.apps.data.installation.port.ParentalControlAuthGateway import foundation.e.apps.data.system.ParentalControlAuthenticator import javax.inject.Inject -interface ParentalControlAuthGateway { - suspend fun awaitAuthentication(): Boolean -} - class ParentalControlAuthGatewayImpl @Inject constructor() : ParentalControlAuthGateway { override suspend fun awaitAuthentication(): Boolean { return ParentalControlAuthenticator.awaitAuthentication() diff --git a/app/src/main/java/foundation/e/apps/data/install/wrapper/StorageSpaceChecker.kt b/app/src/main/java/foundation/e/apps/data/install/wrapper/StorageSpaceCheckerImpl.kt similarity index 92% rename from app/src/main/java/foundation/e/apps/data/install/wrapper/StorageSpaceChecker.kt rename to app/src/main/java/foundation/e/apps/data/install/wrapper/StorageSpaceCheckerImpl.kt index e8a3eaa6b..c3cda13e5 100644 --- a/app/src/main/java/foundation/e/apps/data/install/wrapper/StorageSpaceChecker.kt +++ b/app/src/main/java/foundation/e/apps/data/install/wrapper/StorageSpaceCheckerImpl.kt @@ -19,13 +19,10 @@ package foundation.e.apps.data.install.wrapper import foundation.e.apps.data.installation.model.AppInstall +import foundation.e.apps.data.installation.port.StorageSpaceChecker import foundation.e.apps.data.system.StorageComputer import javax.inject.Inject -interface StorageSpaceChecker { - fun spaceMissing(appInstall: AppInstall): Long -} - class StorageSpaceCheckerImpl @Inject constructor() : StorageSpaceChecker { override fun spaceMissing(appInstall: AppInstall): Long { return StorageComputer.spaceMissing(appInstall) diff --git a/app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesNotificationSender.kt b/app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesNotificationSenderImpl.kt similarity index 92% rename from app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesNotificationSender.kt rename to app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesNotificationSenderImpl.kt index 97a226c93..213a8aea9 100644 --- a/app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesNotificationSender.kt +++ b/app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesNotificationSenderImpl.kt @@ -21,12 +21,9 @@ package foundation.e.apps.data.install.wrapper import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.data.install.updates.UpdatesNotifier +import foundation.e.apps.data.installation.port.UpdatesNotificationSender import javax.inject.Inject -interface UpdatesNotificationSender { - fun showNotification(title: String, message: String) -} - class UpdatesNotificationSenderImpl @Inject constructor( @ApplicationContext private val context: Context ) : UpdatesNotificationSender { diff --git a/app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesTracker.kt b/app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesTrackerImpl.kt similarity index 86% rename from app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesTracker.kt rename to app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesTrackerImpl.kt index 18d71a0f1..963897ac2 100644 --- a/app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesTracker.kt +++ b/app/src/main/java/foundation/e/apps/data/install/wrapper/UpdatesTrackerImpl.kt @@ -20,18 +20,9 @@ package foundation.e.apps.data.install.wrapper import foundation.e.apps.data.application.UpdatesDao import foundation.e.apps.data.installation.model.AppInstall +import foundation.e.apps.data.installation.port.UpdatesTracker import javax.inject.Inject -interface UpdatesTracker { - fun addSuccessfullyUpdatedApp(appInstall: AppInstall) - - fun clearSuccessfullyUpdatedApps() - - fun hasSuccessfulUpdatedApps(): Boolean - - fun successfulUpdatedAppsCount(): Int -} - class UpdatesTrackerImpl @Inject constructor() : UpdatesTracker { override fun addSuccessfullyUpdatedApp(appInstall: AppInstall) { UpdatesDao.addSuccessfullyUpdatedApp(appInstall) diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AgeLimiterTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AgeLimiterTest.kt index 7571a1c63..7a4278f4b 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AgeLimiterTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AgeLimiterTest.kt @@ -25,7 +25,7 @@ import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.install.core.helper.AgeLimiter -import foundation.e.apps.data.install.wrapper.ParentalControlAuthGateway +import foundation.e.apps.data.installation.port.ParentalControlAuthGateway import foundation.e.apps.domain.ValidateAppAgeLimitUseCase import foundation.e.apps.domain.model.ContentRatingValidity import io.mockk.coEvery diff --git a/app/src/test/java/foundation/e/apps/installProcessor/DevicePreconditionsTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/DevicePreconditionsTest.kt index 612e2eacb..a4a23d1d8 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/DevicePreconditionsTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/DevicePreconditionsTest.kt @@ -24,8 +24,8 @@ import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager import foundation.e.apps.data.install.core.helper.DevicePreconditions -import foundation.e.apps.data.install.wrapper.NetworkStatusChecker -import foundation.e.apps.data.install.wrapper.StorageSpaceChecker +import foundation.e.apps.data.installation.port.NetworkStatusChecker +import foundation.e.apps.data.installation.port.StorageSpaceChecker import foundation.e.apps.domain.model.install.Status import io.mockk.coVerify import io.mockk.every diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallationCompletionHandlerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationCompletionHandlerTest.kt index 12db88fdf..b3c59cd8c 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/InstallationCompletionHandlerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationCompletionHandlerTest.kt @@ -25,8 +25,8 @@ import foundation.e.apps.data.installation.repository.AppInstallRepository import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.installation.model.AppInstall import foundation.e.apps.data.install.core.helper.InstallationCompletionHandler -import foundation.e.apps.data.install.wrapper.UpdatesNotificationSender -import foundation.e.apps.data.install.wrapper.UpdatesTracker +import foundation.e.apps.data.installation.port.UpdatesNotificationSender +import foundation.e.apps.data.installation.port.UpdatesTracker import foundation.e.apps.data.preference.PlayStoreAuthStore import foundation.e.apps.domain.model.install.Status import foundation.e.apps.util.MainCoroutineRule diff --git a/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt index 5fd40677e..459432139 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/InstallationEnqueuerTest.kt @@ -40,8 +40,8 @@ import foundation.e.apps.data.install.core.InstallationEnqueuer import foundation.e.apps.data.installation.model.InstallationSource import foundation.e.apps.data.installation.model.InstallationType import foundation.e.apps.data.install.workmanager.InstallWorkManager -import foundation.e.apps.data.install.wrapper.NetworkStatusChecker -import foundation.e.apps.data.install.wrapper.StorageSpaceChecker +import foundation.e.apps.data.installation.port.NetworkStatusChecker +import foundation.e.apps.data.installation.port.StorageSpaceChecker import foundation.e.apps.data.playstore.utils.GplayHttpRequestException import foundation.e.apps.data.preference.PlayStoreAuthStore import foundation.e.apps.domain.model.User diff --git a/data/src/main/java/foundation/e/apps/data/installation/port/NetworkStatusChecker.kt b/data/src/main/java/foundation/e/apps/data/installation/port/NetworkStatusChecker.kt new file mode 100644 index 000000000..2db770769 --- /dev/null +++ b/data/src/main/java/foundation/e/apps/data/installation/port/NetworkStatusChecker.kt @@ -0,0 +1,5 @@ +package foundation.e.apps.data.installation.port + +interface NetworkStatusChecker { + fun isNetworkAvailable(): Boolean +} diff --git a/data/src/main/java/foundation/e/apps/data/installation/port/ParentalControlAuthGateway.kt b/data/src/main/java/foundation/e/apps/data/installation/port/ParentalControlAuthGateway.kt new file mode 100644 index 000000000..19b1b7ba3 --- /dev/null +++ b/data/src/main/java/foundation/e/apps/data/installation/port/ParentalControlAuthGateway.kt @@ -0,0 +1,5 @@ +package foundation.e.apps.data.installation.port + +interface ParentalControlAuthGateway { + suspend fun awaitAuthentication(): Boolean +} diff --git a/data/src/main/java/foundation/e/apps/data/installation/port/StorageSpaceChecker.kt b/data/src/main/java/foundation/e/apps/data/installation/port/StorageSpaceChecker.kt new file mode 100644 index 000000000..f0f51d304 --- /dev/null +++ b/data/src/main/java/foundation/e/apps/data/installation/port/StorageSpaceChecker.kt @@ -0,0 +1,7 @@ +package foundation.e.apps.data.installation.port + +import foundation.e.apps.data.installation.model.AppInstall + +interface StorageSpaceChecker { + fun spaceMissing(appInstall: AppInstall): Long +} diff --git a/data/src/main/java/foundation/e/apps/data/installation/port/UpdatesNotificationSender.kt b/data/src/main/java/foundation/e/apps/data/installation/port/UpdatesNotificationSender.kt new file mode 100644 index 000000000..704d41ce8 --- /dev/null +++ b/data/src/main/java/foundation/e/apps/data/installation/port/UpdatesNotificationSender.kt @@ -0,0 +1,5 @@ +package foundation.e.apps.data.installation.port + +interface UpdatesNotificationSender { + fun showNotification(title: String, message: String) +} diff --git a/data/src/main/java/foundation/e/apps/data/installation/port/UpdatesTracker.kt b/data/src/main/java/foundation/e/apps/data/installation/port/UpdatesTracker.kt new file mode 100644 index 000000000..71addec59 --- /dev/null +++ b/data/src/main/java/foundation/e/apps/data/installation/port/UpdatesTracker.kt @@ -0,0 +1,13 @@ +package foundation.e.apps.data.installation.port + +import foundation.e.apps.data.installation.model.AppInstall + +interface UpdatesTracker { + fun addSuccessfullyUpdatedApp(appInstall: AppInstall) + + fun clearSuccessfullyUpdatedApps() + + fun hasSuccessfulUpdatedApps(): Boolean + + fun successfulUpdatedAppsCount(): Int +} -- GitLab