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

Commit 0a0802f0 authored by Fahim M. Choudhury's avatar Fahim M. Choudhury
Browse files

refactor(updates): parallelize F-Droid signature checks

parent 9da7ca77
Loading
Loading
Loading
Loading
Loading
+60 −22
Original line number Diff line number Diff line
@@ -35,6 +35,8 @@ import foundation.e.apps.data.install.pkg.AppLoungePackageManager
import foundation.e.apps.domain.model.install.Status
import foundation.e.apps.domain.preferences.AppPreferencesRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject
@@ -311,33 +313,41 @@ class UpdatesManagerImpl @Inject constructor(
    private suspend fun getFDroidAppsAndSignatures(
        installedPackageNames: List<String>,
        cleanApkAppsByPackage: Map<String, Application?>,
    ): Map<String, String> {
    ): Map<String, String> = coroutineScope {
        val appsAndSignatures = hashMapOf<String, String>()
        for (packageName in installedPackageNames) {
            updateAppsWithPGPSignature(packageName, appsAndSignatures, cleanApkAppsByPackage)
        val candidates = installedPackageNames.map { packageName ->
            async {
                packageName to updateAppsWithPGPSignature(packageName, cleanApkAppsByPackage)
            }
        return appsAndSignatures
        }

        candidates.forEach { deferred ->
            val (packageName, signature) = deferred.await()
            if (signature != null) {
                appsAndSignatures[packageName] = signature
            }
        }
        return@coroutineScope appsAndSignatures
    }

    private suspend fun updateAppsWithPGPSignature(
        packageName: String,
        appsAndSignatures: HashMap<String, String>,
        cleanApkAppsByPackage: Map<String, Application?>,
    ) {
    ): String? {
        val cleanApkApplication = cleanApkAppsByPackage[packageName]
        val apps = when {
            cleanApkApplication != null -> listOf(cleanApkApplication)
            cleanApkAppsByPackage.containsKey(packageName) -> emptyList()
            else -> applicationRepository.getApplicationDetails(listOf(packageName), Source.OPEN_SOURCE).first
        }
        if (apps.isEmpty()) {
            return
        val app = apps.firstOrNull()?.takeIf { it.package_name.isNotBlank() }
        val signature = if (app == null) {
            null
        } else {
            val pgpSignature = getPgpSignature(app)
            pgpSignature
        }

        if (apps[0].package_name.isBlank()) {
            return
        }
        appsAndSignatures[packageName] = getPgpSignature(apps[0])
        return signature
    }

    private suspend fun getPgpSignature(cleanApkApplication: Application): String {
@@ -371,23 +381,51 @@ class UpdatesManagerImpl @Inject constructor(
    private suspend fun findPackagesMatchingFDroidSignatures(
        installedPackageNames: List<String>,
        cleanApkAppsByPackage: Map<String, Application?>,
    ): List<String> {
    ): List<String> = coroutineScope {
        val fDroidAppsAndSignatures = getFDroidAppsAndSignatures(
            installedPackageNames,
            cleanApkAppsByPackage,
        )

        val fDroidUpdatablePackageNames = fDroidAppsAndSignatures.filter {
            if (it.value.isEmpty()) return@filter false
        val fDroidUpdatablePackageNames = mutableListOf<String>()
        val verificationTasks = fDroidAppsAndSignatures.mapNotNull { (packageName, signature) ->
            if (signature.isEmpty()) {
                return@mapNotNull null
            }

            // For each installed app also present on F-droid, check signature of base APK.
            val baseApkPath = appLoungePackageManager.getBaseApkPath(it.key)
            if (baseApkPath.isEmpty()) return@filter false
            async {
                verifyFdroidSignatureCandidate(packageName, signature)
            }
        }

        verificationTasks.forEach { deferred ->
            val (packageName, verified) = deferred.await()
            if (verified) {
                fDroidUpdatablePackageNames.add(packageName)
            }
        }

            ApkSignatureManager.verifyFdroidSignature(context, baseApkPath, it.value, it.key)
        }.map { it.key }
        return@coroutineScope fDroidUpdatablePackageNames
    }

        return fDroidUpdatablePackageNames
    private suspend fun verifyFdroidSignatureCandidate(
        packageName: String,
        signature: String,
    ): Pair<String, Boolean> {
        val baseApkPath = appLoungePackageManager.getBaseApkPath(packageName)
        if (baseApkPath.isEmpty()) {
            return Pair(packageName, false)
        }

        val verified = withContext(Dispatchers.IO) {
            ApkSignatureManager.verifyFdroidSignature(
                context,
                baseApkPath,
                signature,
                packageName,
            )
        }
        return Pair(packageName, verified)
    }

    private suspend fun getCleanApkDetailsByPackage(