Loading app/src/main/java/foundation/e/apps/data/fused/FusedApiImpl.kt +34 −42 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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 } /* Loading app/src/main/java/foundation/e/apps/data/updates/UpdatesManagerImpl.kt +36 −3 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -106,10 +109,9 @@ class UpdatesManagerImpl @Inject constructor( ) { val gplayStatus = getUpdatesFromApi({ fusedAPIRepository.getApplicationDetails( getGPlayUpdates( gPlayInstalledApps, authData, Origin.GPLAY authData ) }, updateList) Loading Loading @@ -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. Loading app/src/test/java/foundation/e/apps/FusedApiImplTest.kt +33 −64 Original line number Diff line number Diff line Loading @@ -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) Loading @@ -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) Loading @@ -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)) Loading @@ -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()) Loading @@ -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)) Loading app/src/test/java/foundation/e/apps/UpdateManagerImptTest.kt +32 −14 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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)) } } } Loading
app/src/main/java/foundation/e/apps/data/fused/FusedApiImpl.kt +34 −42 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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 } /* Loading
app/src/main/java/foundation/e/apps/data/updates/UpdatesManagerImpl.kt +36 −3 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -106,10 +109,9 @@ class UpdatesManagerImpl @Inject constructor( ) { val gplayStatus = getUpdatesFromApi({ fusedAPIRepository.getApplicationDetails( getGPlayUpdates( gPlayInstalledApps, authData, Origin.GPLAY authData ) }, updateList) Loading Loading @@ -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. Loading
app/src/test/java/foundation/e/apps/FusedApiImplTest.kt +33 −64 Original line number Diff line number Diff line Loading @@ -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) Loading @@ -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) Loading @@ -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)) Loading @@ -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()) Loading @@ -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)) Loading
app/src/test/java/foundation/e/apps/UpdateManagerImptTest.kt +32 −14 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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)) } } }