diff --git a/app/src/main/java/foundation/e/apps/PrivacyInfoViewModel.kt b/app/src/main/java/foundation/e/apps/PrivacyInfoViewModel.kt index 72e6cb8a29846901ac233d2089dc9cde8808d0cf..968d99e948242036a9eae024297db0c4301e491d 100644 --- a/app/src/main/java/foundation/e/apps/PrivacyInfoViewModel.kt +++ b/app/src/main/java/foundation/e/apps/PrivacyInfoViewModel.kt @@ -9,6 +9,7 @@ import foundation.e.apps.api.Result import foundation.e.apps.api.exodus.models.AppPrivacyInfo import foundation.e.apps.api.exodus.repositories.IAppPrivacyInfoRepository import foundation.e.apps.api.fused.data.FusedApp +import foundation.e.apps.utils.modules.CommonUtilsModule.LIST_OF_NULL import javax.inject.Inject import kotlin.math.ceil import kotlin.math.round @@ -27,8 +28,8 @@ class PrivacyInfoViewModel @Inject constructor( private suspend fun fetchEmitAppPrivacyInfo( fusedApp: FusedApp ): Result { - if (fusedApp.trackers.isNotEmpty() && fusedApp.perms.isNotEmpty()) { - val appInfo = AppPrivacyInfo(fusedApp.trackers, fusedApp.perms) + if (fusedApp.trackers.isNotEmpty() && fusedApp.permsFromExodus.isNotEmpty()) { + val appInfo = AppPrivacyInfo(fusedApp.trackers, fusedApp.permsFromExodus) return Result.success(appInfo) } val appPrivacyPrivacyInfoResult = @@ -51,10 +52,14 @@ class PrivacyInfoViewModel @Inject constructor( appPrivacyPrivacyInfoResult: Result, fusedApp: FusedApp ): Result { - fusedApp.trackers = appPrivacyPrivacyInfoResult.data?.trackerList ?: listOf() - if (fusedApp.perms.isEmpty()) { - - fusedApp.perms = appPrivacyPrivacyInfoResult.data?.permissionList ?: listOf() + fusedApp.trackers = appPrivacyPrivacyInfoResult.data?.trackerList ?: LIST_OF_NULL + fusedApp.permsFromExodus = appPrivacyPrivacyInfoResult.data?.permissionList ?: LIST_OF_NULL + if (fusedApp.perms.isEmpty() && fusedApp.permsFromExodus != LIST_OF_NULL) { + /* + * fusedApp.perms is generally populated from remote source like Play Store. + * If it is empty then set the value from permissions from exodus api. + */ + fusedApp.perms = fusedApp.permsFromExodus } return appPrivacyPrivacyInfoResult } @@ -76,6 +81,9 @@ class PrivacyInfoViewModel @Inject constructor( } fun calculatePrivacyScore(fusedApp: FusedApp): Int { + if (fusedApp.permsFromExodus == LIST_OF_NULL) { + return -1 + } val calculateTrackersScore = calculateTrackersScore(fusedApp.trackers.size) val calculatePermissionsScore = calculatePermissionsScore( countAndroidPermissions(fusedApp) @@ -88,7 +96,7 @@ class PrivacyInfoViewModel @Inject constructor( } private fun countAndroidPermissions(fusedApp: FusedApp) = - fusedApp.perms.filter { it.contains("android.permission") }.size + fusedApp.permsFromExodus.filter { it.contains("android.permission") }.size private fun calculateTrackersScore(numberOfTrackers: Int): Int { return if (numberOfTrackers > 5) 0 else 9 - numberOfTrackers diff --git a/app/src/main/java/foundation/e/apps/api/exodus/repositories/AppPrivacyInfoRepositoryImpl.kt b/app/src/main/java/foundation/e/apps/api/exodus/repositories/AppPrivacyInfoRepositoryImpl.kt index e1763e52524cd98d32b7fb64cfb45359fd461145..076b3b5f5a96ab6155df3551b03b2c881ce01fd8 100644 --- a/app/src/main/java/foundation/e/apps/api/exodus/repositories/AppPrivacyInfoRepositoryImpl.kt +++ b/app/src/main/java/foundation/e/apps/api/exodus/repositories/AppPrivacyInfoRepositoryImpl.kt @@ -7,6 +7,7 @@ import foundation.e.apps.api.exodus.Tracker import foundation.e.apps.api.exodus.TrackerDao import foundation.e.apps.api.exodus.models.AppPrivacyInfo import foundation.e.apps.api.getResult +import foundation.e.apps.utils.modules.CommonUtilsModule.LIST_OF_NULL import javax.inject.Inject import javax.inject.Singleton @@ -70,8 +71,17 @@ class AppPrivacyInfoRepositoryImpl @Inject constructor( private fun getAppPrivacyInfo( appTrackerData: List, ): AppPrivacyInfo { + /* + * If the response is empty, that means there is no data on Exodus API about this app, + * i.e. invalid data. + * We signal this by list of "null". + * It is not enough to send just empty lists, as an app can actually have zero trackers + * and zero permissions. This is not to be confused with invalid data. + * + * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5136 + */ if (appTrackerData.isEmpty()) { - return AppPrivacyInfo() + return AppPrivacyInfo(LIST_OF_NULL, LIST_OF_NULL) } val sortedTrackerData = appTrackerData.sortedByDescending { trackerData -> trackerData.versionCode.toLong() } diff --git a/app/src/main/java/foundation/e/apps/api/fused/data/FusedApp.kt b/app/src/main/java/foundation/e/apps/api/fused/data/FusedApp.kt index cf8204f902253fcd0deed6fba43e2225846527c7..b4f349b6305de180baa2a139fbcc32a762554924 100644 --- a/app/src/main/java/foundation/e/apps/api/fused/data/FusedApp.kt +++ b/app/src/main/java/foundation/e/apps/api/fused/data/FusedApp.kt @@ -21,6 +21,7 @@ package foundation.e.apps.api.fused.data import foundation.e.apps.utils.enums.Origin import foundation.e.apps.utils.enums.Status import foundation.e.apps.utils.enums.Type +import foundation.e.apps.utils.modules.CommonUtilsModule.LIST_OF_NULL data class FusedApp( val _id: String = String(), @@ -51,5 +52,15 @@ data class FusedApp( var pwaPlayerDbId: Long = -1, val url: String = String(), var type: Type = Type.NATIVE, - var privacyScore: Int = -1 + var privacyScore: Int = -1, + + /* + * List of permissions from Exodus API. + * This list is now used to calculate the privacy score instead of perms variable above. + * If the value is LIST_OF_NULL - listOf("null"), it means no data is available in Exodus API for this package, + * hence display "N/A" + * + * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5136 + */ + var permsFromExodus: List = LIST_OF_NULL, ) diff --git a/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt b/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt index e1116823617c715070935d46d568e694f35209e0..fa101d6286e07e9c1054e1e8eeed6f88f2a7022a 100644 --- a/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt +++ b/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt @@ -58,6 +58,7 @@ import foundation.e.apps.manager.pkg.PkgManagerModule import foundation.e.apps.utils.enums.Origin import foundation.e.apps.utils.enums.Status import foundation.e.apps.utils.enums.User +import foundation.e.apps.utils.modules.CommonUtilsModule.LIST_OF_NULL import foundation.e.apps.utils.modules.PWAManagerModule import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -235,10 +236,13 @@ class ApplicationFragment : Fragment(R.layout.fragment_application) { ).show(childFragmentManager, TAG) } appTrackers.setOnClickListener { + val fusedApp = applicationViewModel.fusedApp.value var trackers = - privacyInfoViewModel.getTrackerListText(applicationViewModel.fusedApp.value) + privacyInfoViewModel.getTrackerListText(fusedApp) - if (trackers.isNotEmpty()) { + if (fusedApp?.trackers == LIST_OF_NULL) { + trackers = getString(R.string.tracker_information_not_found) + } else if (trackers.isNotEmpty()) { trackers += "

" + getString( R.string.privacy_computed_using_text, EXODUS_URL diff --git a/app/src/main/java/foundation/e/apps/applicationlist/model/ApplicationListRVAdapter.kt b/app/src/main/java/foundation/e/apps/applicationlist/model/ApplicationListRVAdapter.kt index c70c36d4a26bc0506eac8b55ef59578bdaafe204..324f2e6b1df4744e6d129763dd32d1621c2ea3c5 100644 --- a/app/src/main/java/foundation/e/apps/applicationlist/model/ApplicationListRVAdapter.kt +++ b/app/src/main/java/foundation/e/apps/applicationlist/model/ApplicationListRVAdapter.kt @@ -246,8 +246,9 @@ class ApplicationListRVAdapter( ) { privacyInfoViewModel.getAppPrivacyInfoLiveData(searchApp).observe(lifecycleOwner) { showPrivacyScore() - if (it.isSuccess()) { - searchApp.privacyScore = privacyInfoViewModel.calculatePrivacyScore(searchApp) + val calculatedScore = privacyInfoViewModel.calculatePrivacyScore(searchApp) + if (it.isSuccess() && calculatedScore != -1) { + searchApp.privacyScore = calculatedScore appPrivacyScore.text = view.context.getString( R.string.privacy_rating_out_of, searchApp.privacyScore.toString() diff --git a/app/src/main/java/foundation/e/apps/utils/modules/CommonUtilsModule.kt b/app/src/main/java/foundation/e/apps/utils/modules/CommonUtilsModule.kt index 950e9791c4c5406f2092911b0f84e70a852d5a23..12af86294e6e096a8d68624c078474d878b5332f 100644 --- a/app/src/main/java/foundation/e/apps/utils/modules/CommonUtilsModule.kt +++ b/app/src/main/java/foundation/e/apps/utils/modules/CommonUtilsModule.kt @@ -43,6 +43,8 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) object CommonUtilsModule { + val LIST_OF_NULL = listOf("null") + /** * Check supported ABIs by device * @return An ordered list of ABIs supported by this device diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2293cc8b99612eee60e33c3dda3150710ba05aeb..c7f94ead7e0b0e32b6718b6c83bd73aba7822f29 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -141,6 +141,7 @@ No runtime android permission found! Computed using <a href="%1$s"> Exodus Privacy Analysis No Tracker Found! + No tracker information available for this app. updateCheckIntervals