Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Verified Commit 4c31d240 authored by Fahim M. Choudhury's avatar Fahim M. Choudhury
Browse files

refactor: replace AppInstallationManager with AppManager

AppManager and its implementation is now the center point of managing app installation.
parent ce7e01d0
Loading
Loading
Loading
Loading
+0 −3
Original line number Diff line number Diff line
@@ -9,11 +9,9 @@
    <ID>InstanceOfCheckForException:GPlayHttpClient.kt$GPlayHttpClient$e is SocketTimeoutException</ID>
    <ID>InvalidPackageDeclaration:Trackers.kt$package foundation.e.apps.data.exodus</ID>
    <ID>LargeClass:ApplicationFragment.kt$ApplicationFragment : TimeoutFragment</ID>
    <ID>LongParameterList:AppManagerImpl.kt$AppManagerImpl$( @Named("cacheDir") private val cacheDir: String, private val downloadManager: DownloadManager, private val notificationManager: NotificationManager, private val appInstallRepository: AppInstallRepository, private val pwaManager: PwaManager, private val appLoungePackageManager: AppLoungePackageManager, @Named("download") private val downloadNotificationChannel: NotificationChannel, @Named("update") private val updateNotificationChannel: NotificationChannel, @ApplicationContext private val context: Context )</ID>
    <ID>LongParameterList:ApplicationDialogFragment.kt$ApplicationDialogFragment$( title: String, message: String, @DrawableRes drawableResId: Int = -1, drawable: Drawable? = null, positiveButtonText: String = "", positiveButtonAction: (() -&gt; Unit)? = null, cancelButtonText: String = "", cancelButtonAction: (() -&gt; Unit)? = null, cancelable: Boolean = true, onDismissListener: (() -&gt; Unit)? = null, )</ID>
    <ID>LongParameterList:ApplicationListRVAdapter.kt$ApplicationListRVAdapter$( private val applicationInstaller: ApplicationInstaller, private val privacyInfoViewModel: PrivacyInfoViewModel, private val appInfoFetchViewModel: AppInfoFetchViewModel, private val mainActivityViewModel: MainActivityViewModel, private val currentDestinationId: Int, private var lifecycleOwner: LifecycleOwner?, private var paidAppHandler: ((Application) -&gt; Unit)? = null )</ID>
    <ID>LongParameterList:EglExtensionProvider.kt$EglExtensionProvider$( egl10: EGL10, eglDisplay: EGLDisplay, eglConfig: EGLConfig?, ai: IntArray, ai1: IntArray?, set: MutableSet&lt;String&gt; )</ID>
    <ID>LongParameterList:MainActivityViewModel.kt$MainActivityViewModel$( private val appLoungeDataStore: AppLoungeDataStore, private val applicationRepository: ApplicationRepository, private val AppInstallationManagerImpl: AppInstallationManagerImpl, 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, )</ID>
    <ID>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, )</ID>
    <ID>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, )</ID>
    <ID>ProtectedMemberInFinalClass:ApplicationListFragment.kt$ApplicationListFragment$// protected to avoid SyntheticAccessor protected val args: ApplicationListFragmentArgs by navArgs()</ID>
@@ -71,7 +69,6 @@
    <ID>TooGenericExceptionCaught:UpdatesWorker.kt$UpdatesWorker$e: Throwable</ID>
    <ID>TooGenericExceptionThrown:AnonymousLoginManager.kt$AnonymousLoginManager$throw Exception( "Error fetching Anonymous credentials\n" + "Network code: ${response.code}\n" + "Success: ${response.isSuccessful}" + response.errorString.run { if (isNotBlank()) "\nError message: $this" else "" } )</ID>
    <ID>TooManyFunctions:AppLoungePackageManager.kt$AppLoungePackageManager</ID>
    <ID>TooManyFunctions:AppManager.kt$AppManager</ID>
    <ID>TooManyFunctions:AppManagerImpl.kt$AppManagerImpl : AppManager</ID>
    <ID>TooManyFunctions:ApplicationListFragment.kt$ApplicationListFragment : TimeoutFragmentApplicationInstaller</ID>
    <ID>TooManyFunctions:ApplicationRepository.kt$ApplicationRepository</ID>
+0 −6
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import foundation.e.apps.data.install.AppInstallationManagerImpl
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
@@ -32,7 +31,6 @@ import foundation.e.apps.data.install.wrapper.ParentalControlAuthGatewayImpl
import foundation.e.apps.data.install.wrapper.StorageSpaceCheckerImpl
import foundation.e.apps.data.install.wrapper.UpdatesNotificationSenderImpl
import foundation.e.apps.data.install.wrapper.UpdatesTrackerImpl
import foundation.e.apps.data.installation.port.AppInstallationManager
import foundation.e.apps.data.installation.port.InstallationCompletionNotifier
import foundation.e.apps.data.installation.port.InstallationDownloadStatusUpdater
import foundation.e.apps.data.installation.port.NetworkStatusChecker
@@ -70,10 +68,6 @@ interface AppInstallationModule {
    @Singleton
    fun bindNetworkStatusChecker(checker: DeviceNetworkStatusChecker): NetworkStatusChecker

    @Binds
    @Singleton
    fun bindAppInstallationManager(appInstallationManagerImpl: AppInstallationManagerImpl): AppInstallationManager

    @Binds
    @Singleton
    fun bindInstallationDownloadStatusUpdater(
+2 −2
Original line number Diff line number Diff line
@@ -22,13 +22,13 @@ import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import foundation.e.apps.data.application.AppManager
import foundation.e.apps.data.exodus.repositories.AppPrivacyInfoRepositoryImpl
import foundation.e.apps.data.exodus.repositories.IAppPrivacyInfoRepository
import foundation.e.apps.data.exodus.repositories.PrivacyScoreRepository
import foundation.e.apps.data.exodus.repositories.PrivacyScoreRepositoryImpl
import foundation.e.apps.data.fdroid.FDroidRepository
import foundation.e.apps.data.fdroid.IFdroidRepository
import foundation.e.apps.data.install.AppManager
import foundation.e.apps.data.install.AppManagerImpl
import javax.inject.Singleton

@@ -41,7 +41,7 @@ interface RepositoryModule {

    @Singleton
    @Binds
    fun getFusedManagerImpl(appManagerImpl: AppManagerImpl): AppManager
    fun getAppManager(appManagerImpl: AppManagerImpl): AppManager

    @Singleton
    @Binds
+51 −2
Original line number Diff line number Diff line
@@ -28,10 +28,13 @@ 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.application.AppManager
import foundation.e.apps.data.fdroid.FDroidRepository
import foundation.e.apps.data.install.download.data.DownloadProgressLD
import foundation.e.apps.data.install.pkg.AppLoungePackageManager
import foundation.e.apps.data.install.pkg.PwaManager
import foundation.e.apps.data.install.sharedlib.SharedLibraryManager
import foundation.e.apps.data.install.workmanager.InstallWorkManager
import foundation.e.apps.data.installation.model.AppInstall
import foundation.e.apps.data.installation.model.InstallationType
import foundation.e.apps.data.installation.repository.AppInstallRepository
@@ -59,7 +62,8 @@ class AppManagerImpl @Inject constructor(
    private val sharedLibraryManager: SharedLibraryManager,
    @Named("download") private val downloadNotificationChannel: NotificationChannel,
    @Named("update") private val updateNotificationChannel: NotificationChannel,
    @ApplicationContext private val context: Context
    @ApplicationContext private val context: Context,
    private val fDroidRepository: FDroidRepository
) : AppManager {

    @Inject
@@ -77,11 +81,56 @@ class AppManagerImpl @Inject constructor(
        }
    }

    override suspend fun addDownload(appInstall: AppInstall) {
    override suspend fun enqueueAppInstall(appInstall: AppInstall) {
        appInstall.status = Status.QUEUED
        appInstallRepository.addDownload(appInstall)
    }

    override suspend fun addDownload(appInstall: AppInstall): Boolean {
        val existingFusedDownload = getDownloadById(appInstall)
        val canAddDownload = when {
            isInstallWorkRunning(existingFusedDownload, appInstall) -> false
            // We don't want to add anything if it already exists without INSTALLATION_ISSUE
            existingFusedDownload != null && !isStatusEligibleToInstall(existingFusedDownload) -> false
            else -> true
        }

        if (canAddDownload) {
            enqueueAppInstall(appInstall)
        }

        return canAddDownload
    }

    private fun isInstallWorkRunning(
        existingAppInstall: AppInstall?,
        appInstall: AppInstall
    ) =
        existingAppInstall != null && InstallWorkManager.checkWorkIsAlreadyAvailable(
            context,
            appInstall.id
        )

    private fun isStatusEligibleToInstall(existingAppInstall: AppInstall) =
        listOf(
            Status.UNAVAILABLE,
            Status.INSTALLATION_ISSUE,
            Status.PURCHASE_NEEDED
        ).contains(existingAppInstall.status)

    override suspend fun isFDroidApplicationSigned(
        context: Context,
        appInstall: AppInstall
    ): Boolean {
        val apkFilePath = getBaseApkPath(appInstall)
        return fDroidRepository.isFDroidApplicationSigned(
            context,
            appInstall.packageName,
            apkFilePath,
            appInstall.signature
        )
    }

    override suspend fun getDownloadById(appInstall: AppInstall): AppInstall? {
        return appInstallRepository.getDownloadById(appInstall.id)
    }
+112 −0
Original line number Diff line number Diff line
@@ -18,128 +18,29 @@

package foundation.e.apps.data.install

import android.content.Context
import androidx.lifecycle.LiveData
import dagger.hilt.android.qualifiers.ApplicationContext
import foundation.e.apps.OpenForTesting
import foundation.e.apps.data.application.AppManager
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.workmanager.InstallWorkManager
import foundation.e.apps.data.installation.model.AppInstall
import foundation.e.apps.data.installation.port.AppInstallationManager
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 AppInstallationManagerImpl @Inject constructor(
    @ApplicationContext private val context: Context,
    private val appManager: AppManager,
    private val fDroidRepository: FDroidRepository
) : AppInstallationManager {

    override fun createNotificationChannels() {
        return appManager.createNotificationChannels()
    }

    override suspend fun downloadApp(appInstall: AppInstall, isUpdate: Boolean) {
        return appManager.downloadApp(appInstall, isUpdate)
    }

    override fun moveOBBFileToOBBDirectory(appInstall: AppInstall) {
        return appManager.moveOBBFilesToOBBDirectory(appInstall)
    }

    override suspend fun addDownload(appInstall: AppInstall): Boolean {
        val existingFusedDownload = appManager.getDownloadById(appInstall)
        val canAddDownload = when {
            isInstallWorkRunning(existingFusedDownload, appInstall) -> false
            // We don't want to add anything if it already exists without INSTALLATION_ISSUE
            existingFusedDownload != null && !isStatusEligibleToInstall(existingFusedDownload) -> false
            else -> true
        }

        if (canAddDownload) {
            appManager.addDownload(appInstall)
        }

        return canAddDownload
    }

    private fun isStatusEligibleToInstall(existingAppInstall: AppInstall) =
        listOf(
            Status.UNAVAILABLE,
            Status.INSTALLATION_ISSUE,
            Status.PURCHASE_NEEDED
        ).contains(existingAppInstall.status)

    private fun isInstallWorkRunning(
        existingAppInstall: AppInstall?,
        appInstall: AppInstall
    ) =
        existingAppInstall != null && InstallWorkManager.checkWorkIsAlreadyAvailable(
            context,
            appInstall.id
        )

    override suspend fun addFusedDownloadPurchaseNeeded(appInstall: AppInstall) {
        appManager.insertAppInstallPurchaseNeeded(appInstall)
    }

    override suspend fun getDownloadList(): List<AppInstall> {
        return appManager.getDownloadList()
    }

    override fun getDownloadLiveList(): LiveData<List<AppInstall>> {
        return appManager.getDownloadLiveList()
    }

    override suspend fun getFusedDownload(downloadId: Long, packageName: String): AppInstall {
        return appManager.getFusedDownload(downloadId, packageName)
    }

    override suspend fun updateDownloadStatus(appInstall: AppInstall, status: Status) {
        return appManager.updateDownloadStatus(appInstall, status)
    }

    override suspend fun cancelDownload(appInstall: AppInstall) {
        return appManager.cancelDownload(appInstall, "")
    }

    override suspend fun cancelDownload(appInstall: AppInstall, packageName: String) {
        return appManager.cancelDownload(appInstall, packageName)
    }

    override suspend fun installationIssue(appInstall: AppInstall) {
        return appManager.reportInstallationIssue(appInstall)
    }

    override suspend fun updateAwaiting(appInstall: AppInstall) {
        appManager.updateAwaiting(appInstall)
    }

    override suspend fun updateUnavailable(appInstall: AppInstall) {
        appManager.updateUnavailable(appInstall)
    }

    override suspend fun updateFusedDownload(appInstall: AppInstall) {
        appManager.updateAppInstall(appInstall)
    }

    override fun validateFusedDownload(appInstall: AppInstall) =
        appInstall.packageName.isNotEmpty() && appInstall.downloadURLList.isNotEmpty()
class DownloadProgressTracker @Inject constructor(
    private val appManager: AppManager
) {

    suspend fun calculateProgress(
        application: Application?,
        progress: DownloadProgress
    ): Int {
        application?.let { app ->
            val appDownload = getDownloadList()
            val appDownload = appManager.getDownloadList()
                .singleOrNull { it.id.contentEquals(app._id) && it.packageName.contentEquals(app.package_name) }
                ?: return 0
            return calculateProgress(appDownload, progress)
@@ -182,7 +83,7 @@ class AppInstallationManagerImpl @Inject constructor(
        progress: DownloadProgress
    ): Pair<Long, Long> {
        application?.let { app ->
            val appDownload = getDownloadList()
            val appDownload = appManager.getDownloadList()
                .singleOrNull { it.id.contentEquals(app._id) }
            val downloadingMap = progress.totalSizeBytes.filter { item ->
                appDownload?.downloadIdMap?.keys?.contains(item.key) == true
@@ -208,22 +109,4 @@ class AppInstallationManagerImpl @Inject constructor(
        }
        return null
    }

    override suspend fun isFDroidApplicationSigned(context: Context, appInstall: AppInstall): Boolean {
        val apkFilePath = appManager.getBaseApkPath(appInstall)
        return fDroidRepository.isFDroidApplicationSigned(
            context,
            appInstall.packageName,
            apkFilePath,
            appInstall.signature
        )
    }

    override fun isFusedDownloadInstalled(appInstall: AppInstall): Boolean {
        return appManager.isAppInstalled(appInstall)
    }

    override fun getFusedDownloadPackageStatus(appInstall: AppInstall): Status {
        return appManager.getInstallationStatus(appInstall)
    }
}
Loading