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

Commit 953390d2 authored by Sayantan Roychowdhury's avatar Sayantan Roychowdhury
Browse files

Issue 7135: Improve update page filter

parent 2492d046
Loading
Loading
Loading
Loading
+34 −42
Original line number Diff line number Diff line
@@ -574,6 +574,7 @@ class FusedApiImpl @Inject constructor(
        return Pair(fusedApp, result.getResultStatus())
    }

    // Warning - GPlay results may not have proper geo-restriction information.
    override suspend fun getApplicationDetails(
        packageNameList: List<String>,
        authData: AuthData,
@@ -714,52 +715,43 @@ class FusedApiImpl @Inject constructor(
     * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5720
     */
    override suspend fun getAppFilterLevel(fusedApp: FusedApp, authData: AuthData?): FilterLevel {
        if (fusedApp.package_name.isBlank()) {
            return FilterLevel.UNKNOWN
        return when {
            fusedApp.package_name.isBlank() -> FilterLevel.UNKNOWN
            !fusedApp.isFree && fusedApp.price.isBlank() -> FilterLevel.UI
            fusedApp.origin == Origin.CLEANAPK -> FilterLevel.NONE
            !isRestricted(fusedApp) -> FilterLevel.NONE
            authData == null -> FilterLevel.UNKNOWN // cannot determine for gplay app
            !isApplicationVisible(fusedApp) -> FilterLevel.DATA
            fusedApp.originalSize == 0L -> FilterLevel.UI
            !isDownloadable(fusedApp) -> FilterLevel.UI
            else -> FilterLevel.NONE
        }
        if (fusedApp.origin == Origin.CLEANAPK) {
            /*
             * Whitelist all open source apps.
             * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5785
             */
            return FilterLevel.NONE
        }
        if (authData == null) {
            return if (fusedApp.origin == Origin.GPLAY) FilterLevel.UNKNOWN
            else FilterLevel.NONE
        }

        if (!fusedApp.isFree && fusedApp.price.isBlank()) {
            return FilterLevel.UI
    }

        if (fusedApp.restriction != Constants.Restriction.NOT_RESTRICTED) {
            /*
             * Check if app details can be shown. If not then remove the app from lists.
    /**
     * Some apps are simply not visible.
     * Example: com.skype.m2
     */
            try {
                gplayRepository.getAppDetails(fusedApp.package_name)
            } catch (e: Exception) {
                return FilterLevel.DATA
    private suspend fun isApplicationVisible(fusedApp: FusedApp): Boolean {
        return kotlin.runCatching { gplayRepository.getAppDetails(fusedApp.package_name) }.isSuccess
    }

            /*
             * If the app can be shown, check if the app is downloadable.
             * If not then change "Install" button to "N/A"
    /**
     * Some apps are visible but not downloadable.
     * Example: com.riotgames.league.wildrift
     */
            try {
    private suspend fun isDownloadable(fusedApp: FusedApp): Boolean {
        return kotlin.runCatching {
            gplayRepository.getDownloadInfo(
                fusedApp.package_name,
                fusedApp.latest_version_code,
                fusedApp.offer_type,
            )
            } catch (e: Exception) {
                return FilterLevel.UI
            }
        } else if (fusedApp.originalSize == 0L) {
            return FilterLevel.UI
        }.isSuccess
    }
        return FilterLevel.NONE

    private fun isRestricted(fusedApp: FusedApp): Boolean {
        return fusedApp.restriction != Constants.Restriction.NOT_RESTRICTED
    }

    /*
+36 −3
Original line number Diff line number Diff line
@@ -39,6 +39,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope

class UpdatesManagerImpl @Inject constructor(
    @ApplicationContext private val context: Context,
@@ -106,10 +109,9 @@ class UpdatesManagerImpl @Inject constructor(
        ) {

            val gplayStatus = getUpdatesFromApi({
                fusedAPIRepository.getApplicationDetails(
                getGPlayUpdates(
                    gPlayInstalledApps,
                    authData,
                    Origin.GPLAY
                    authData
                )
            }, updateList)

@@ -222,6 +224,37 @@ class UpdatesManagerImpl @Inject constructor(
        return apiResult.second
    }

    /**
     * Bulk info from gplay api is not providing correct geo restriction status of apps.
     * So we get all individual app information asynchronously.
     * Example: in.startv.hotstar.dplus
     * Issue: https://gitlab.e.foundation/e/backlog/-/issues/7135
     */
    private suspend fun getGPlayUpdates(
        packageNames: List<String>,
        authData: AuthData
    ): Pair<List<FusedApp>, ResultStatus> {

        val appsResults = coroutineScope {
            val deferredResults = packageNames.map { packageName ->
                async {
                    fusedAPIRepository.getApplicationDetails(
                        "",
                        packageName,
                        authData,
                        Origin.GPLAY
                    )
                }
            }
            deferredResults.awaitAll()
        }

        val status = appsResults.find { it.second != ResultStatus.OK }?.second ?: ResultStatus.OK
        val appsList = appsResults.map { it.first }

        return Pair(appsList, status)
    }

    /**
     * Takes a list of package names and for the apps present on F-Droid,
     * returns key value pairs of package names and their signatures.
+33 −64
Original line number Diff line number Diff line
@@ -473,44 +473,37 @@ class FusedApiImplTest {

    @Test
    fun `getAppFilterLevel when app is CleanApk`() = runTest {
        val fusedApp = FusedApp(
            _id = "113",
            name = "Demo Three",
            package_name = "foundation.e.demothree",
            latest_version_code = 123,
            origin = Origin.CLEANAPK
        )
        val fusedApp = getFusedAppForFilterLevelTest()

        val filterLevel = fusedAPIImpl.getAppFilterLevel(fusedApp, AUTH_DATA)
        assertEquals("getAppFilterLevel", FilterLevel.NONE, filterLevel)
    }

    @Test
    fun `getAppFilterLevel when Authdata is NULL`() = runTest {
        val fusedApp = FusedApp(
    private fun getFusedAppForFilterLevelTest(isFree: Boolean = true) = FusedApp(
        _id = "113",
        name = "Demo Three",
        package_name = "foundation.e.demothree",
        latest_version_code = 123,
            origin = Origin.CLEANAPK
        origin = Origin.CLEANAPK,
        originalSize = -1,
        isFree = isFree,
        price = ""
    )

    @Test
    fun `getAppFilterLevel when Authdata is NULL`() = runTest {
        val fusedApp = getFusedAppForFilterLevelTest()

        val filterLevel = fusedAPIImpl.getAppFilterLevel(fusedApp, null)
        assertEquals("getAppFilterLevel", FilterLevel.NONE, filterLevel)
    }

    @Test
    fun `getAppFilterLevel when app is restricted and paid and no price`() = runTest {
        val fusedApp = FusedApp(
            _id = "113",
            name = "Demo Three",
            package_name = "foundation.e.demothree",
            latest_version_code = 123,
            origin = Origin.GPLAY,
            restriction = Constants.Restriction.UNKNOWN,
            isFree = false,
            price = ""
        )
        val fusedApp = getFusedAppForFilterLevelTest(false).apply {
            this.origin = Origin.GPLAY
            this.restriction = Constants.Restriction.UNKNOWN
        }

        val filterLevel = fusedAPIImpl.getAppFilterLevel(fusedApp, AUTH_DATA)
        assertEquals("getAppFilterLevel", FilterLevel.UI, filterLevel)
@@ -518,16 +511,10 @@ class FusedApiImplTest {

    @Test
    fun `getAppFilterLevel when app is not_restricted and paid and no price`() = runTest {
        val fusedApp = FusedApp(
            _id = "113",
            name = "Demo Three",
            package_name = "foundation.e.demothree",
            latest_version_code = 123,
            origin = Origin.GPLAY,
            restriction = Constants.Restriction.NOT_RESTRICTED,
            isFree = false,
            price = ""
        )
        val fusedApp = getFusedAppForFilterLevelTest(false).apply {
            this.origin = Origin.GPLAY
            this.restriction = Constants.Restriction.NOT_RESTRICTED
        }

        val filterLevel = fusedAPIImpl.getAppFilterLevel(fusedApp, AUTH_DATA)
        assertEquals("getAppFilterLevel", FilterLevel.UI, filterLevel)
@@ -536,16 +523,10 @@ class FusedApiImplTest {
    @Test
    fun `getAppFilterLevel when app is restricted and getAppDetails and getDownloadDetails returns success`() =
        runTest {
            val fusedApp = FusedApp(
                _id = "113",
                name = "Demo Three",
                package_name = "foundation.e.demothree",
                latest_version_code = 123,
                origin = Origin.GPLAY,
                restriction = Constants.Restriction.UNKNOWN,
                isFree = true,
                price = ""
            )
            val fusedApp = getFusedAppForFilterLevelTest().apply {
                this.origin = Origin.GPLAY
                this.restriction = Constants.Restriction.UNKNOWN
            }

            Mockito.`when`(gPlayAPIRepository.getAppDetails(fusedApp.package_name))
                .thenReturn(App(fusedApp.package_name))
@@ -564,16 +545,10 @@ class FusedApiImplTest {

    @Test
    fun `getAppFilterLevel when app is restricted and getAppDetails throws exception`() = runTest {
        val fusedApp = FusedApp(
            _id = "113",
            name = "Demo Three",
            package_name = "foundation.e.demothree",
            latest_version_code = 123,
            origin = Origin.GPLAY,
            restriction = Constants.Restriction.UNKNOWN,
            isFree = true,
            price = ""
        )
        val fusedApp = getFusedAppForFilterLevelTest().apply {
            this.origin = Origin.GPLAY
            this.restriction = Constants.Restriction.UNKNOWN
        }

        Mockito.`when`(gPlayAPIRepository.getAppDetails(fusedApp.package_name))
            .thenThrow(RuntimeException())
@@ -590,16 +565,10 @@ class FusedApiImplTest {

    @Test
    fun `getAppFilterLevel when app is restricted and getDownoadInfo throws exception`() = runTest {
        val fusedApp = FusedApp(
            _id = "113",
            name = "Demo Three",
            package_name = "foundation.e.demothree",
            latest_version_code = 123,
            origin = Origin.GPLAY,
            restriction = Constants.Restriction.UNKNOWN,
            isFree = true,
            price = ""
        )
        val fusedApp = getFusedAppForFilterLevelTest().apply {
            this.origin = Origin.GPLAY
            this.restriction = Constants.Restriction.UNKNOWN
        }

        Mockito.`when`(gPlayAPIRepository.getAppDetails(fusedApp.package_name))
            .thenReturn(App(fusedApp.package_name))
+32 −14
Original line number Diff line number Diff line
@@ -81,20 +81,21 @@ class UpdateManagerImptTest {

    val authData = AuthData("e@e.email", "AtadyMsIAtadyM")

    val applicationInfo = mutableListOf<ApplicationInfo>(
        ApplicationInfo().apply { this.packageName = "foundation.e.demoone" },
        ApplicationInfo().apply { this.packageName = "foundation.e.demotwo" },
        ApplicationInfo().apply { this.packageName = "foundation.e.demothree" }
    )

    @Before
    fun setup() {
        MockitoAnnotations.openMocks(this)
        faultyAppRepository = FaultyAppRepository(FakeFaultyAppDao())
        preferenceModule = FakePreferenceModule(context)
        pkgManagerModule = FakePkgManagerModule(context, getGplayApps())
        updatesManagerImpl =
            UpdatesManagerImpl(context, pkgManagerModule, fusedAPIRepository, faultyAppRepository, preferenceModule, fdroidRepository, blockedAppRepository)
        updatesManagerImpl = UpdatesManagerImpl(
            context,
            pkgManagerModule,
            fusedAPIRepository,
            faultyAppRepository,
            preferenceModule,
            fdroidRepository,
            blockedAppRepository
        )
    }

    @Test
@@ -330,14 +331,31 @@ class UpdateManagerImptTest {
                eq(Origin.CLEANAPK)
            )
        ).thenReturn(openSourceUpdates)

        Mockito.`when`(fusedAPIRepository.getApplicationCategoryPreference())
            .thenReturn(selectedApplicationSources)

        if (gplayUpdates.first.isNotEmpty()) {
            Mockito.`when`(
                fusedAPIRepository.getApplicationDetails(
                    any(),
                    any(),
                    any(),
                    eq(Origin.GPLAY)
                )
        ).thenReturn(gplayUpdates)
            ).thenReturn(
                Pair(gplayUpdates.first.first(), ResultStatus.OK),
                Pair(gplayUpdates.first[1], ResultStatus.OK)
            )
        } else {
            Mockito.`when`(
                fusedAPIRepository.getApplicationDetails(
                    any(),
                    any(),
                    any(),
                    eq(Origin.GPLAY)
                )
            ).thenReturn(Pair(FusedApp(), ResultStatus.TIMEOUT))
        }
    }
}