From 64b3973515a21611b45c52ce0e3c5088948ca75c Mon Sep 17 00:00:00 2001 From: hasibprince Date: Fri, 14 Apr 2023 05:57:09 +0600 Subject: [PATCH] Updated app specific exodus url --- .../e/apps/api/exodus/ExodusTrackerApi.kt | 2 +- .../e/apps/api/exodus/TrackerApiResponse.kt | 3 +- .../apps/api/exodus/models/AppPrivacyInfo.kt | 2 +- .../AppPrivacyInfoRepositoryImpl.kt | 75 +++++++++++++------ .../e/apps/api/fused/data/FusedApp.kt | 1 + .../e/apps/application/ApplicationFragment.kt | 45 ++++++----- .../apps/application/ApplicationViewModel.kt | 3 + .../workmanager/AppInstallProcessor.kt | 10 +-- .../foundation/e/apps/utils/CommonUtils.kt | 10 +++ .../e/apps/exodus/FakeExoudsTrackerApi.kt | 4 +- 10 files changed, 105 insertions(+), 50 deletions(-) create mode 100644 app/src/main/java/foundation/e/apps/utils/CommonUtils.kt diff --git a/app/src/main/java/foundation/e/apps/api/exodus/ExodusTrackerApi.kt b/app/src/main/java/foundation/e/apps/api/exodus/ExodusTrackerApi.kt index 324ca9ed0..4fe785603 100644 --- a/app/src/main/java/foundation/e/apps/api/exodus/ExodusTrackerApi.kt +++ b/app/src/main/java/foundation/e/apps/api/exodus/ExodusTrackerApi.kt @@ -12,7 +12,7 @@ interface ExodusTrackerApi { } @GET("trackers?v={date}") - suspend fun getTrackerList(@Path("date") date: String): Response + suspend fun getTrackerList(@Query("date") date: String): Response @GET("search/{appHandle}/details") suspend fun getTrackerInfoOfApp( diff --git a/app/src/main/java/foundation/e/apps/api/exodus/TrackerApiResponse.kt b/app/src/main/java/foundation/e/apps/api/exodus/TrackerApiResponse.kt index 09d427051..54cd8707c 100644 --- a/app/src/main/java/foundation/e/apps/api/exodus/TrackerApiResponse.kt +++ b/app/src/main/java/foundation/e/apps/api/exodus/TrackerApiResponse.kt @@ -9,10 +9,11 @@ data class TrackerInfo( ) data class Report( - val id: Long = System.currentTimeMillis(), + val report: Long = -1L, @Json(name = "updated") val updatedAt: String, @Json(name = "version_name") val version: String, @Json(name = "version_code") val versionCode: String, + val source: String, val trackers: List, val permissions: List = listOf() ) diff --git a/app/src/main/java/foundation/e/apps/api/exodus/models/AppPrivacyInfo.kt b/app/src/main/java/foundation/e/apps/api/exodus/models/AppPrivacyInfo.kt index 8ebf20741..d7074a293 100644 --- a/app/src/main/java/foundation/e/apps/api/exodus/models/AppPrivacyInfo.kt +++ b/app/src/main/java/foundation/e/apps/api/exodus/models/AppPrivacyInfo.kt @@ -1,3 +1,3 @@ package foundation.e.apps.api.exodus.models -data class AppPrivacyInfo(val trackerList: List = listOf(), val permissionList: List = listOf()) +data class AppPrivacyInfo(val trackerList: List = listOf(), val permissionList: List = listOf(), val reportId: Long = -1L) 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 2fc815dad..1c284a49a 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 @@ -26,8 +26,9 @@ import foundation.e.apps.api.exodus.TrackerDao import foundation.e.apps.api.exodus.models.AppPrivacyInfo import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.api.getResult +import foundation.e.apps.utils.enums.Origin import foundation.e.apps.utils.modules.CommonUtilsModule.LIST_OF_NULL -import java.text.SimpleDateFormat +import foundation.e.apps.utils.getFormattedString import java.util.Date import java.util.Locale import javax.inject.Inject @@ -35,12 +36,12 @@ import javax.inject.Singleton import kotlin.math.ceil import kotlin.math.round + @Singleton class AppPrivacyInfoRepositoryImpl @Inject constructor( private val exodusTrackerApi: ExodusTrackerApi, private val trackerDao: TrackerDao ) : IAppPrivacyInfoRepository { - companion object { private const val MAX_TRACKER_SCORE = 9 private const val MIN_TRACKER_SCORE = 0 @@ -50,6 +51,9 @@ class AppPrivacyInfoRepositoryImpl @Inject constructor( 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" } private var trackers: List = listOf() @@ -59,7 +63,7 @@ class AppPrivacyInfoRepositoryImpl @Inject constructor( appHandle: String ): Result { if (fusedApp.trackers.isNotEmpty() && fusedApp.permsFromExodus.isNotEmpty()) { - val appInfo = AppPrivacyInfo(fusedApp.trackers, fusedApp.permsFromExodus) + val appInfo = AppPrivacyInfo(fusedApp.trackers, fusedApp.permsFromExodus, fusedApp.reportId) return Result.success(appInfo) } @@ -69,33 +73,44 @@ class AppPrivacyInfoRepositoryImpl @Inject constructor( fusedApp.latest_version_code, ) } + if (appTrackerInfoResult.isSuccess()) { - val appPrivacyPrivacyInfoResult = - handleAppPrivacyInfoResultSuccess(appTrackerInfoResult) - updateFusedApp(fusedApp, appPrivacyPrivacyInfoResult) - return appPrivacyPrivacyInfoResult + return parsePrivacyInfo(fusedApp, appTrackerInfoResult) } return Result.error(extractErrorMessage(appTrackerInfoResult)) } + private suspend fun parsePrivacyInfo( + fusedApp: FusedApp, + appTrackerInfoResult: Result> + ): Result { + val appPrivacyPrivacyInfoResult = + handleAppPrivacyInfoResultSuccess(fusedApp, appTrackerInfoResult) + + updateFusedApp(fusedApp, appPrivacyPrivacyInfoResult) + return appPrivacyPrivacyInfoResult + } + private fun updateFusedApp( fusedApp: FusedApp, appPrivacyPrivacyInfoResult: Result ) { fusedApp.trackers = appPrivacyPrivacyInfoResult.data?.trackerList ?: LIST_OF_NULL fusedApp.permsFromExodus = appPrivacyPrivacyInfoResult.data?.permissionList ?: LIST_OF_NULL + fusedApp.reportId = appPrivacyPrivacyInfoResult.data?.reportId ?: -1L if (fusedApp.permsFromExodus != LIST_OF_NULL) { fusedApp.perms = fusedApp.permsFromExodus } } private suspend fun handleAppPrivacyInfoResultSuccess( + fusedApp: FusedApp, appTrackerResult: Result>, ): Result { if (trackers.isEmpty()) { generateTrackerList() } - return createAppPrivacyInfoResult(appTrackerResult) + return createAppPrivacyInfo(fusedApp, appTrackerResult) } private suspend fun generateTrackerList() { @@ -108,7 +123,8 @@ class AppPrivacyInfoRepositoryImpl @Inject constructor( } private suspend fun generateTrackerListFromExodusApi() { - val result = getResult { exodusTrackerApi.getTrackerList(getDate()) } + val date = Date().getFormattedString(DATE_FORMAT, Locale("en")) + val result = getResult { exodusTrackerApi.getTrackerList(date) } if (result.isSuccess()) { result.data?.let { val trackerList = it.trackers.values.toList() @@ -118,25 +134,22 @@ class AppPrivacyInfoRepositoryImpl @Inject constructor( } } - private fun getDate(): String { - val dateFormat = SimpleDateFormat("ddMMyyyy", Locale("en")) - return dateFormat.format(Date()) - } - private fun extractErrorMessage(appTrackerResult: Result>): String { return appTrackerResult.message ?: "Unknown Error" } - private fun createAppPrivacyInfoResult( + private fun createAppPrivacyInfo( + fusedApp: FusedApp, appTrackerResult: Result>, ): Result { appTrackerResult.data?.let { - return Result.success(getAppPrivacyInfo(it)) + return Result.success(getAppPrivacyInfo(fusedApp, it)) } return Result.error(extractErrorMessage(appTrackerResult)) } private fun getAppPrivacyInfo( + fusedApp: FusedApp, appTrackerData: List, ): AppPrivacyInfo { /* @@ -151,16 +164,33 @@ class AppPrivacyInfoRepositoryImpl @Inject constructor( if (appTrackerData.isEmpty()) { return AppPrivacyInfo(LIST_OF_NULL, LIST_OF_NULL) } + + val latestTrackerData = getLatestTrackerData(fusedApp, appTrackerData) + ?: return AppPrivacyInfo(LIST_OF_NULL, LIST_OF_NULL) + + val appTrackers = extractAppTrackers(latestTrackerData) + val permissions = latestTrackerData.permissions + return AppPrivacyInfo(appTrackers, permissions, latestTrackerData.report) + } + + private fun getLatestTrackerData( + fusedApp: FusedApp, + appTrackerData: List + ): Report? { + val source = if (fusedApp.origin == Origin.CLEANAPK) SOURCE_FDROID else SOURCE_GOOGLE + val filteredAppTrackerData = appTrackerData.filter { it.source == source } + if (filteredAppTrackerData.isEmpty()) { + return null + } + val sortedTrackerData = - appTrackerData.sortedByDescending { trackerData -> trackerData.versionCode.toLong() } - val appTrackers = extractAppTrackers(sortedTrackerData) - val permissions = sortedTrackerData[0].permissions - return AppPrivacyInfo(appTrackers, permissions) + filteredAppTrackerData.sortedByDescending { trackerData -> trackerData.versionCode.toLong() } + return sortedTrackerData[0] } - private fun extractAppTrackers(sortedTrackerData: List): List { + private fun extractAppTrackers(latestTrackerData: Report): List { return trackers.filter { - sortedTrackerData[0].trackers.contains(it.id) + latestTrackerData.trackers.contains(it.id) }.map { it.name } } @@ -168,6 +198,7 @@ class AppPrivacyInfoRepositoryImpl @Inject constructor( if (fusedApp.permsFromExodus == LIST_OF_NULL) { return -1 } + val calculateTrackersScore = calculateTrackersScore(fusedApp.trackers.size) val calculatePermissionsScore = calculatePermissionsScore( countAndroidPermissions(fusedApp) 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 e54c71371..410a237e2 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 @@ -32,6 +32,7 @@ data class FusedApp( val description: String = String(), var perms: List = emptyList(), var trackers: List = emptyList(), + var reportId: Long = -1L, val icon_image_path: String = String(), val last_modified: String = String(), var latest_version_code: Int = -1, 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 6da3d4929..dfbe19b10 100644 --- a/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt +++ b/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt @@ -257,20 +257,9 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { ).show(childFragmentManager, TAG) } appTrackers.setOnClickListener { - val fusedApp = applicationViewModel.fusedApp.value?.first + val fusedApp = applicationViewModel.getFusedApp() var trackers = - privacyInfoViewModel.getTrackerListText(fusedApp) - - 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, - generateExodusUrl() - ) - } else { - trackers = getString(R.string.no_tracker_found) - } + buildTrackersString(fusedApp) ApplicationDialogFragment( R.drawable.ic_tracker, @@ -281,6 +270,24 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { } } + private fun buildTrackersString(fusedApp: FusedApp?): String { + var trackers = + privacyInfoViewModel.getTrackerListText(fusedApp) + + 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, + generateExodusUrl() + ) + } else { + trackers = getString(R.string.no_tracker_found) + } + + return trackers + } + private fun updateAppInformation( it: FusedApp, ) { @@ -327,6 +334,7 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { } appPrivacyScoreLayout.setOnClickListener { + ApplicationDialogFragment( R.drawable.ic_lock, getString(R.string.privacy_score), @@ -444,7 +452,7 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { val installButton = binding.downloadInclude.installButton val downloadPB = binding.downloadInclude.progressLayout val appSize = binding.downloadInclude.appSize - val fusedApp = applicationViewModel.fusedApp.value?.first ?: FusedApp() + val fusedApp = applicationViewModel.getFusedApp() ?: FusedApp() mainActivityViewModel.verifyUiFilter(fusedApp) { if (!fusedApp.filterLevel.isInitialized()) { @@ -756,12 +764,13 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { private fun generateExodusUrl(): String { // if app info not loaded yet, pass the default exodus homePage url - if (applicationViewModel.fusedApp.value == null) { + val fusedApp = applicationViewModel.getFusedApp() + if (fusedApp == null || fusedApp.permsFromExodus == LIST_OF_NULL) { return EXODUS_URL } - val packageName = applicationViewModel.fusedApp.value!!.first.package_name - return "$EXODUS_REPORT_URL${Locale.getDefault().language}/reports/$packageName/latest" + val reportId = applicationViewModel.fusedApp.value!!.first.reportId + return "$EXODUS_REPORT_URL${Locale.getDefault().language}/reports/$reportId" } private fun fetchAppTracker(fusedApp: FusedApp) { @@ -782,7 +791,7 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { private fun updatePrivacyScore() { val privacyScore = - privacyInfoViewModel.getPrivacyScore(applicationViewModel.fusedApp.value?.first) + privacyInfoViewModel.getPrivacyScore(applicationViewModel.getFusedApp()) if (privacyScore != -1) { val appPrivacyScore = binding.ratingsInclude.appPrivacyScore appPrivacyScore.text = getString( diff --git a/app/src/main/java/foundation/e/apps/application/ApplicationViewModel.kt b/app/src/main/java/foundation/e/apps/application/ApplicationViewModel.kt index d4e9b43c0..6ecc40c5c 100644 --- a/app/src/main/java/foundation/e/apps/application/ApplicationViewModel.kt +++ b/app/src/main/java/foundation/e/apps/application/ApplicationViewModel.kt @@ -167,6 +167,9 @@ class ApplicationViewModel @Inject constructor( return permissionString } + fun getFusedApp(): FusedApp? { + return fusedApp.value?.first + } fun handleRatingFormat(rating: Double): String { return fusedManagerRepository.handleRatingFormat(rating) } diff --git a/app/src/main/java/foundation/e/apps/manager/workmanager/AppInstallProcessor.kt b/app/src/main/java/foundation/e/apps/manager/workmanager/AppInstallProcessor.kt index 29ff73ab7..b35c40b8a 100644 --- a/app/src/main/java/foundation/e/apps/manager/workmanager/AppInstallProcessor.kt +++ b/app/src/main/java/foundation/e/apps/manager/workmanager/AppInstallProcessor.kt @@ -28,14 +28,15 @@ import foundation.e.apps.manager.fused.FusedManagerRepository import foundation.e.apps.updates.UpdatesNotifier import foundation.e.apps.utils.enums.ResultStatus import foundation.e.apps.utils.enums.Status +import foundation.e.apps.utils.getFormattedString import foundation.e.apps.utils.modules.DataStoreManager import kotlinx.coroutines.flow.transformWhile import timber.log.Timber import java.text.NumberFormat -import java.text.SimpleDateFormat import java.util.Date import javax.inject.Inject + class AppInstallProcessor @Inject constructor( @ApplicationContext private val context: Context, private val databaseRepository: DatabaseRepository, @@ -47,6 +48,7 @@ class AppInstallProcessor @Inject constructor( companion object { private const val TAG = "AppInstallProcessor" + private const val DATE_FORMAT = "dd/MM/yyyy-HH:mm" } suspend fun processInstall( @@ -138,10 +140,8 @@ class AppInstallProcessor @Inject constructor( } private fun showNotificationOnUpdateEnded() { - val date = Date(System.currentTimeMillis()) val locale = dataStoreManager.getAuthData().locale - val dateFormat = - SimpleDateFormat("dd/MM/yyyy-HH:mm", locale) + val date = Date().getFormattedString(DATE_FORMAT, locale) val numberOfUpdatedApps = NumberFormat.getNumberInstance(locale) .format(UpdatesDao.successfulUpdatedApps.size) .toString() @@ -149,7 +149,7 @@ class AppInstallProcessor @Inject constructor( UpdatesNotifier.showNotification( context, context.getString(R.string.update), context.getString( - R.string.message_last_update_triggered, numberOfUpdatedApps, dateFormat.format(date) + R.string.message_last_update_triggered, numberOfUpdatedApps, date ) ) } diff --git a/app/src/main/java/foundation/e/apps/utils/CommonUtils.kt b/app/src/main/java/foundation/e/apps/utils/CommonUtils.kt new file mode 100644 index 000000000..b28cf5c4d --- /dev/null +++ b/app/src/main/java/foundation/e/apps/utils/CommonUtils.kt @@ -0,0 +1,10 @@ +package foundation.e.apps.utils + +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +fun Date.getFormattedString(format: String, locale: Locale = Locale.getDefault()): String { + val dateFormat = SimpleDateFormat(format, locale) + return dateFormat.format(this) +} \ No newline at end of file diff --git a/app/src/test/java/foundation/e/apps/exodus/FakeExoudsTrackerApi.kt b/app/src/test/java/foundation/e/apps/exodus/FakeExoudsTrackerApi.kt index a668cd42f..3da41794d 100644 --- a/app/src/test/java/foundation/e/apps/exodus/FakeExoudsTrackerApi.kt +++ b/app/src/test/java/foundation/e/apps/exodus/FakeExoudsTrackerApi.kt @@ -40,8 +40,8 @@ class FakeExoudsTrackerApi : ExodusTrackerApi { if (appHandle.isEmpty()) { return Response.error(404, null) } - val reportOne = Report(System.currentTimeMillis(), "12-12-12", "1.2.3", "123", listOf(1,2,3), listOf()) - val reportTwo = Report(System.currentTimeMillis(), "12-12-12", "1.2.3", "123", listOf(1,2,3), listOf()) + val reportOne = Report(System.currentTimeMillis(), "12-12-12", "1.2.3", "123", "google", listOf(1,2,3), listOf()) + val reportTwo = Report(System.currentTimeMillis(), "12-12-12", "1.2.3", "123", "fdroid", listOf(1,2,3), listOf()) return Response.success(listOf(reportOne, reportTwo)) } } \ No newline at end of file -- GitLab