From 32ea69531585079e4213856a57382548c6b84be6 Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Fri, 16 Jun 2023 17:54:03 +0600 Subject: [PATCH 1/2] 1353-Refactor_privacy_score_calculation_code issue: https://gitlab.e.foundation/e/os/backlog/-/issues/1353 User can see the privacyScore calculation code (gitlab source code link) from the app's detail page. So any update to AppPrivacyInfoRepositoryImpl can breaks the link unwantedly (can link to wrong line no for example). For this we want to move the privacyScore calculation to separate class so, the link can't be broken easily. --- .../AppPrivacyInfoRepositoryImpl.kt | 33 ------- .../repositories/IAppPrivacyInfoRepository.kt | 1 - .../repositories/PrivacyScoreRepository.kt | 26 ++++++ .../PrivacyScoreRepositoryImpl.kt | 68 +++++++++++++++ .../foundation/e/apps/di/RepositoryModule.kt | 6 ++ .../e/apps/ui/PrivacyInfoViewModel.kt | 4 +- .../AppPrivacyInfoRepositoryImplTest.kt | 54 +----------- .../exodus/PrivacyScoreRepositoryImplTest.kt | 87 +++++++++++++++++++ 8 files changed, 192 insertions(+), 87 deletions(-) create mode 100644 app/src/main/java/foundation/e/apps/data/exodus/repositories/PrivacyScoreRepository.kt create mode 100644 app/src/main/java/foundation/e/apps/data/exodus/repositories/PrivacyScoreRepositoryImpl.kt create mode 100644 app/src/test/java/foundation/e/apps/exodus/PrivacyScoreRepositoryImplTest.kt diff --git a/app/src/main/java/foundation/e/apps/data/exodus/repositories/AppPrivacyInfoRepositoryImpl.kt b/app/src/main/java/foundation/e/apps/data/exodus/repositories/AppPrivacyInfoRepositoryImpl.kt index ab32cf0c9..934324205 100644 --- a/app/src/main/java/foundation/e/apps/data/exodus/repositories/AppPrivacyInfoRepositoryImpl.kt +++ b/app/src/main/java/foundation/e/apps/data/exodus/repositories/AppPrivacyInfoRepositoryImpl.kt @@ -42,14 +42,6 @@ class AppPrivacyInfoRepositoryImpl @Inject constructor( private val trackerDao: TrackerDao ) : IAppPrivacyInfoRepository { companion object { - private const val MAX_TRACKER_SCORE = 9 - private const val MIN_TRACKER_SCORE = 0 - private const val MAX_PERMISSION_SCORE = 10 - private const val MIN_PERMISSION_SCORE = 0 - private const val THRESHOLD_OF_NON_ZERO_TRACKER_SCORE = 5 - private const val THRESHOLD_OF_NON_ZERO_PERMISSION_SCORE = 9 - private const val FACTOR_OF_PERMISSION_SCORE = 0.2 - private const val DIVIDER_OF_PERMISSION_SCORE = 2.0 private const val DATE_FORMAT = "ddMMyyyy" private const val SOURCE_FDROID = "fdroid" private const val SOURCE_GOOGLE = "google" @@ -192,29 +184,4 @@ class AppPrivacyInfoRepositoryImpl @Inject constructor( latestTrackerData.trackers.contains(it.id) }.map { it.name } } - - override fun calculatePrivacyScore(fusedApp: FusedApp): Int { - if (fusedApp.permsFromExodus == LIST_OF_NULL) { - return -1 - } - - val calculateTrackersScore = calculateTrackersScore(fusedApp.trackers.size) - val calculatePermissionsScore = calculatePermissionsScore( - countAndroidPermissions(fusedApp) - ) - return calculateTrackersScore + calculatePermissionsScore - } - - private fun countAndroidPermissions(fusedApp: FusedApp) = - fusedApp.permsFromExodus.filter { it.contains("android.permission") }.size - - private fun calculateTrackersScore(numberOfTrackers: Int): Int { - return if (numberOfTrackers > THRESHOLD_OF_NON_ZERO_TRACKER_SCORE) MIN_TRACKER_SCORE else MAX_TRACKER_SCORE - numberOfTrackers - } - - private fun calculatePermissionsScore(numberOfPermission: Int): Int { - return if (numberOfPermission > THRESHOLD_OF_NON_ZERO_PERMISSION_SCORE) MIN_PERMISSION_SCORE else round( - FACTOR_OF_PERMISSION_SCORE * ceil((MAX_PERMISSION_SCORE - numberOfPermission) / DIVIDER_OF_PERMISSION_SCORE) - ).toInt() - } } diff --git a/app/src/main/java/foundation/e/apps/data/exodus/repositories/IAppPrivacyInfoRepository.kt b/app/src/main/java/foundation/e/apps/data/exodus/repositories/IAppPrivacyInfoRepository.kt index 213c96cf0..6f4e9d82c 100644 --- a/app/src/main/java/foundation/e/apps/data/exodus/repositories/IAppPrivacyInfoRepository.kt +++ b/app/src/main/java/foundation/e/apps/data/exodus/repositories/IAppPrivacyInfoRepository.kt @@ -6,5 +6,4 @@ import foundation.e.apps.data.fused.data.FusedApp interface IAppPrivacyInfoRepository { suspend fun getAppPrivacyInfo(fusedApp: FusedApp, appHandle: String): Result - fun calculatePrivacyScore(fusedApp: FusedApp): Int } diff --git a/app/src/main/java/foundation/e/apps/data/exodus/repositories/PrivacyScoreRepository.kt b/app/src/main/java/foundation/e/apps/data/exodus/repositories/PrivacyScoreRepository.kt new file mode 100644 index 000000000..347eb2ead --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/exodus/repositories/PrivacyScoreRepository.kt @@ -0,0 +1,26 @@ +/* + * Copyright MURENA SAS 2023 + * 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.exodus.repositories + +import foundation.e.apps.data.fused.data.FusedApp + +interface PrivacyScoreRepository { + + fun calculatePrivacyScore(fusedApp: FusedApp): Int +} diff --git a/app/src/main/java/foundation/e/apps/data/exodus/repositories/PrivacyScoreRepositoryImpl.kt b/app/src/main/java/foundation/e/apps/data/exodus/repositories/PrivacyScoreRepositoryImpl.kt new file mode 100644 index 000000000..871e82640 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/exodus/repositories/PrivacyScoreRepositoryImpl.kt @@ -0,0 +1,68 @@ +/* + * Copyright MURENA SAS 2023 + * 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.exodus.repositories + +import foundation.e.apps.data.fused.data.FusedApp +import foundation.e.apps.di.CommonUtilsModule +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.math.ceil +import kotlin.math.round + +@Singleton +class PrivacyScoreRepositoryImpl @Inject constructor() : PrivacyScoreRepository { + + override fun calculatePrivacyScore(fusedApp: FusedApp): Int { + if (fusedApp.permsFromExodus == CommonUtilsModule.LIST_OF_NULL) { + return -1 + } + + val calculateTrackersScore = calculateTrackersScore(fusedApp.trackers.size) + val calculatePermissionsScore = calculatePermissionsScore( + countAndroidPermissions(fusedApp) + ) + return calculateTrackersScore + calculatePermissionsScore + } + + private fun calculateTrackersScore(numberOfTrackers: Int): Int { + return if (numberOfTrackers > THRESHOLD_OF_NON_ZERO_TRACKER_SCORE) MIN_TRACKER_SCORE else MAX_TRACKER_SCORE - numberOfTrackers + } + + private fun countAndroidPermissions(fusedApp: FusedApp) = + fusedApp.permsFromExodus.filter { it.contains("android.permission") }.size + + private fun calculatePermissionsScore(numberOfPermission: Int): Int { + return if (numberOfPermission > THRESHOLD_OF_NON_ZERO_PERMISSION_SCORE) MIN_PERMISSION_SCORE else round( + FACTOR_OF_PERMISSION_SCORE * ceil((MAX_PERMISSION_SCORE - numberOfPermission) / DIVIDER_OF_PERMISSION_SCORE) + ).toInt() + } + + // please do not put in the top of the class, as it can break the privacy calculation source code link. + // for more info: https://gitlab.e.foundation/e/os/backlog/-/issues/1353 + companion object { + private const val MAX_TRACKER_SCORE = 9 + private const val MIN_TRACKER_SCORE = 0 + private const val MAX_PERMISSION_SCORE = 10 + private const val MIN_PERMISSION_SCORE = 0 + private const val THRESHOLD_OF_NON_ZERO_TRACKER_SCORE = 5 + private const val THRESHOLD_OF_NON_ZERO_PERMISSION_SCORE = 9 + private const val FACTOR_OF_PERMISSION_SCORE = 0.2 + private const val DIVIDER_OF_PERMISSION_SCORE = 2.0 + } +} diff --git a/app/src/main/java/foundation/e/apps/di/RepositoryModule.kt b/app/src/main/java/foundation/e/apps/di/RepositoryModule.kt index 818511a20..0a1ac2714 100644 --- a/app/src/main/java/foundation/e/apps/di/RepositoryModule.kt +++ b/app/src/main/java/foundation/e/apps/di/RepositoryModule.kt @@ -6,6 +6,8 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent 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.fused.FusedApi @@ -32,4 +34,8 @@ interface RepositoryModule { @Singleton @Binds fun getFusedApi(fusedApiImpl: FusedApiImpl): FusedApi + + @Singleton + @Binds + fun getPrivacyScoreRepository(privacyScoreRepositoryImpl: PrivacyScoreRepositoryImpl): PrivacyScoreRepository } diff --git a/app/src/main/java/foundation/e/apps/ui/PrivacyInfoViewModel.kt b/app/src/main/java/foundation/e/apps/ui/PrivacyInfoViewModel.kt index 6dfd01650..7f24fa3c5 100644 --- a/app/src/main/java/foundation/e/apps/ui/PrivacyInfoViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/PrivacyInfoViewModel.kt @@ -7,12 +7,14 @@ import dagger.hilt.android.lifecycle.HiltViewModel import foundation.e.apps.data.Result import foundation.e.apps.data.exodus.models.AppPrivacyInfo import foundation.e.apps.data.exodus.repositories.IAppPrivacyInfoRepository +import foundation.e.apps.data.exodus.repositories.PrivacyScoreRepository import foundation.e.apps.data.fused.data.FusedApp import javax.inject.Inject @HiltViewModel class PrivacyInfoViewModel @Inject constructor( private val privacyInfoRepository: IAppPrivacyInfoRepository, + private val privacyScoreRepository: PrivacyScoreRepository, ) : ViewModel() { fun getAppPrivacyInfoLiveData(fusedApp: FusedApp): LiveData> { @@ -48,7 +50,7 @@ class PrivacyInfoViewModel @Inject constructor( fun getPrivacyScore(fusedApp: FusedApp?): Int { fusedApp?.let { - return privacyInfoRepository.calculatePrivacyScore(it) + return privacyScoreRepository.calculatePrivacyScore(it) } return -1 } diff --git a/app/src/test/java/foundation/e/apps/exodus/AppPrivacyInfoRepositoryImplTest.kt b/app/src/test/java/foundation/e/apps/exodus/AppPrivacyInfoRepositoryImplTest.kt index b055f4d3b..399aee6fb 100644 --- a/app/src/test/java/foundation/e/apps/exodus/AppPrivacyInfoRepositoryImplTest.kt +++ b/app/src/test/java/foundation/e/apps/exodus/AppPrivacyInfoRepositoryImplTest.kt @@ -22,7 +22,6 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule import foundation.e.apps.data.enums.Status import foundation.e.apps.data.exodus.repositories.AppPrivacyInfoRepositoryImpl import foundation.e.apps.data.fused.data.FusedApp -import foundation.e.apps.di.CommonUtilsModule.LIST_OF_NULL import foundation.e.apps.util.MainCoroutineRule import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest @@ -52,7 +51,8 @@ class AppPrivacyInfoRepositoryImplTest { fun setup() { fakeExodusTrackerApi = FakeExoudsTrackerApi() fakeTrackerDao = FakeTrackerDao() - appPrivacyInfoRepository = AppPrivacyInfoRepositoryImpl(fakeExodusTrackerApi, fakeTrackerDao) + appPrivacyInfoRepository = + AppPrivacyInfoRepositoryImpl(fakeExodusTrackerApi, fakeTrackerDao) } @Test @@ -98,54 +98,4 @@ class AppPrivacyInfoRepositoryImplTest { val result = appPrivacyInfoRepository.getAppPrivacyInfo(fusedApp, fusedApp.package_name) assertEquals("getAppPrivacyInfo", 2, result.data?.trackerList?.size) } - - @Test - fun calculatePrivacyScoreWhenNoTrackers() { - val fusedApp = FusedApp( - _id = "113", - status = Status.UNAVAILABLE, - name = "Demo Three", - package_name = "a.b.c", - latest_version_code = 123, - is_pwa = true, - permsFromExodus = listOf(), - perms = listOf(), - trackers = listOf() - ) - val privacyScore = appPrivacyInfoRepository.calculatePrivacyScore(fusedApp) - assertEquals("getAppPrivacyInfo", 10, privacyScore) - } - - @Test - fun calculatePrivacyScoreWhenPermsAreNotAvailable() { - val fusedApp = FusedApp( - _id = "113", - status = Status.UNAVAILABLE, - name = "Demo Three", - package_name = "a.b.c", - latest_version_code = 123, - is_pwa = true, - perms = listOf(), - trackers = listOf() - ) - val privacyScore = appPrivacyInfoRepository.calculatePrivacyScore(fusedApp) - assertEquals("getAppPrivacyInfo", -1, privacyScore) - } - - @Test - fun calculatePrivacyScoreWhenTrackersAreNotAvailable() { - val fusedApp = FusedApp( - _id = "113", - status = Status.UNAVAILABLE, - name = "Demo Three", - package_name = "a.b.c", - latest_version_code = 123, - is_pwa = true, - permsFromExodus = listOf(), - perms = listOf(), - trackers = LIST_OF_NULL - ) - val privacyScore = appPrivacyInfoRepository.calculatePrivacyScore(fusedApp) - assertEquals("getAppPrivacyInfo", 9, privacyScore) - } } diff --git a/app/src/test/java/foundation/e/apps/exodus/PrivacyScoreRepositoryImplTest.kt b/app/src/test/java/foundation/e/apps/exodus/PrivacyScoreRepositoryImplTest.kt new file mode 100644 index 000000000..b6a8c25f8 --- /dev/null +++ b/app/src/test/java/foundation/e/apps/exodus/PrivacyScoreRepositoryImplTest.kt @@ -0,0 +1,87 @@ +/* + * Copyright MURENA SAS 2023 + * 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.exodus + +import foundation.e.apps.data.enums.Status +import foundation.e.apps.data.exodus.repositories.PrivacyScoreRepositoryImpl +import foundation.e.apps.data.fused.data.FusedApp +import foundation.e.apps.di.CommonUtilsModule +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class PrivacyScoreRepositoryImplTest { + + private lateinit var privacyScoreRepository: PrivacyScoreRepositoryImpl + + @Before + fun setup() { + privacyScoreRepository = PrivacyScoreRepositoryImpl() + } + + @Test + fun calculatePrivacyScoreWhenNoTrackers() { + val fusedApp = FusedApp( + _id = "113", + status = Status.UNAVAILABLE, + name = "Demo Three", + package_name = "a.b.c", + latest_version_code = 123, + is_pwa = true, + permsFromExodus = listOf(), + perms = listOf(), + trackers = listOf() + ) + val privacyScore = privacyScoreRepository.calculatePrivacyScore(fusedApp) + Assert.assertEquals("failed to retrieve valid privacy score", 10, privacyScore) + } + + @Test + fun calculatePrivacyScoreWhenPermsAreNotAvailable() { + val fusedApp = FusedApp( + _id = "113", + status = Status.UNAVAILABLE, + name = "Demo Three", + package_name = "a.b.c", + latest_version_code = 123, + is_pwa = true, + perms = listOf(), + trackers = listOf() + ) + val privacyScore = privacyScoreRepository.calculatePrivacyScore(fusedApp) + Assert.assertEquals("failed to retrieve valid privacy score", -1, privacyScore) + } + + @Test + fun calculatePrivacyScoreWhenTrackersAreNotAvailable() { + val fusedApp = FusedApp( + _id = "113", + status = Status.UNAVAILABLE, + name = "Demo Three", + package_name = "a.b.c", + latest_version_code = 123, + is_pwa = true, + permsFromExodus = listOf(), + perms = listOf(), + trackers = CommonUtilsModule.LIST_OF_NULL + ) + val privacyScore = privacyScoreRepository.calculatePrivacyScore(fusedApp) + Assert.assertEquals("failed to retrieve valid privacy score", 9, privacyScore) + } +} -- GitLab From d9cd47f3d61d5897bd6ea6899c7e85eb895a0fa5 Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Fri, 16 Jun 2023 18:02:47 +0600 Subject: [PATCH 2/2] Update privacy score calculation source code link issue: https://gitlab.e.foundation/e/os/backlog/-/issues/1353 --- .../foundation/e/apps/ui/application/ApplicationFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/foundation/e/apps/ui/application/ApplicationFragment.kt b/app/src/main/java/foundation/e/apps/ui/application/ApplicationFragment.kt index 5c40b3b2f..c7c565403 100644 --- a/app/src/main/java/foundation/e/apps/ui/application/ApplicationFragment.kt +++ b/app/src/main/java/foundation/e/apps/ui/application/ApplicationFragment.kt @@ -125,7 +125,7 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { companion object { private const val PRIVACY_SCORE_SOURCE_CODE_URL = - "https://gitlab.e.foundation/e/os/apps/-/blob/main/app/src/main/java/foundation/e/apps/data/exodus/repositories/AppPrivacyInfoRepositoryImpl.kt#L196" + "https://gitlab.e.foundation/e/os/apps/-/blob/main/app/src/main/java/foundation/e/apps/data/exodus/repositories/PrivacyScoreRepositoryImpl.kt#L31" private const val EXODUS_URL = "https://exodus-privacy.eu.org" private const val EXODUS_REPORT_URL = "https://reports.exodus-privacy.eu.org/" private const val PRIVACY_GUIDELINE_URL = "https://doc.e.foundation/privacy_score" -- GitLab