From 890dc45718043c37519eb4db0ef33f5121b2f55d Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 2 Jun 2022 13:58:39 -0300 Subject: [PATCH] [db] Implement interfaces from index library so we can use UpdateChecker and CompatibilityChecker with our DB classes --- ...eCheckerTest.kt => DbUpdateCheckerTest.kt} | 6 +- .../main/java/org/fdroid/database/AppDao.kt | 2 +- .../main/java/org/fdroid/database/AppPrefs.kt | 7 +- .../{UpdateChecker.kt => DbUpdateChecker.kt} | 76 ++++++++----------- .../main/java/org/fdroid/database/Version.kt | 36 ++++----- .../java/org/fdroid/database/VersionDao.kt | 10 +-- 6 files changed, 59 insertions(+), 78 deletions(-) rename database/src/androidTest/java/org/fdroid/database/{UpdateCheckerTest.kt => DbUpdateCheckerTest.kt} (86%) rename database/src/main/java/org/fdroid/database/{UpdateChecker.kt => DbUpdateChecker.kt} (62%) diff --git a/database/src/androidTest/java/org/fdroid/database/UpdateCheckerTest.kt b/database/src/androidTest/java/org/fdroid/database/DbUpdateCheckerTest.kt similarity index 86% rename from database/src/androidTest/java/org/fdroid/database/UpdateCheckerTest.kt rename to database/src/androidTest/java/org/fdroid/database/DbUpdateCheckerTest.kt index 5a864664b..4f58159c4 100644 --- a/database/src/androidTest/java/org/fdroid/database/UpdateCheckerTest.kt +++ b/database/src/androidTest/java/org/fdroid/database/DbUpdateCheckerTest.kt @@ -10,15 +10,15 @@ import kotlin.time.ExperimentalTime import kotlin.time.measureTime @RunWith(AndroidJUnit4::class) -internal class UpdateCheckerTest : DbTest() { +internal class DbUpdateCheckerTest : DbTest() { - private lateinit var updateChecker: UpdateChecker + private lateinit var updateChecker: DbUpdateChecker @Before override fun createDb() { super.createDb() // TODO mock packageManager and maybe move to unit tests - updateChecker = UpdateChecker(db, context.packageManager) + updateChecker = DbUpdateChecker(db, context.packageManager) } @Test diff --git a/database/src/main/java/org/fdroid/database/AppDao.kt b/database/src/main/java/org/fdroid/database/AppDao.kt index 9939cf3bb..e5e5b3919 100644 --- a/database/src/main/java/org/fdroid/database/AppDao.kt +++ b/database/src/main/java/org/fdroid/database/AppDao.kt @@ -464,7 +464,7 @@ internal interface AppDaoInt : AppDao { override fun getNumberOfAppsInCategory(category: String): Int /** - * Used by [UpdateChecker] to get specific apps with available updates. + * Used by [DbUpdateChecker] to get specific apps with available updates. */ @Transaction @SuppressWarnings(CURSOR_MISMATCH) // no anti-features needed here diff --git a/database/src/main/java/org/fdroid/database/AppPrefs.kt b/database/src/main/java/org/fdroid/database/AppPrefs.kt index c32a0bb3c..11593c5a1 100644 --- a/database/src/main/java/org/fdroid/database/AppPrefs.kt +++ b/database/src/main/java/org/fdroid/database/AppPrefs.kt @@ -2,18 +2,19 @@ package org.fdroid.database import androidx.room.Entity import androidx.room.PrimaryKey +import org.fdroid.PackagePreference @Entity public data class AppPrefs( @PrimaryKey val packageId: String, - val ignoreVersionCodeUpdate: Long = 0, + override val ignoreVersionCodeUpdate: Long = 0, // This is named like this, because it hit a Room bug when joining with Version table // which had exactly the same field. internal val appPrefReleaseChannels: List? = null, -) { +) : PackagePreference { public val ignoreAllUpdates: Boolean get() = ignoreVersionCodeUpdate == Long.MAX_VALUE - public val releaseChannels: List get() = appPrefReleaseChannels ?: emptyList() + public override val releaseChannels: List get() = appPrefReleaseChannels ?: emptyList() public fun shouldIgnoreUpdate(versionCode: Long): Boolean = ignoreVersionCodeUpdate >= versionCode diff --git a/database/src/main/java/org/fdroid/database/UpdateChecker.kt b/database/src/main/java/org/fdroid/database/DbUpdateChecker.kt similarity index 62% rename from database/src/main/java/org/fdroid/database/UpdateChecker.kt rename to database/src/main/java/org/fdroid/database/DbUpdateChecker.kt index b7eee6de5..cdf2cae30 100644 --- a/database/src/main/java/org/fdroid/database/UpdateChecker.kt +++ b/database/src/main/java/org/fdroid/database/DbUpdateChecker.kt @@ -6,9 +6,10 @@ import android.content.pm.PackageManager import android.content.pm.PackageManager.GET_SIGNATURES import android.os.Build import org.fdroid.CompatibilityCheckerImpl -import org.fdroid.index.IndexUtils +import org.fdroid.PackagePreference +import org.fdroid.UpdateChecker -public class UpdateChecker( +public class DbUpdateChecker( db: FDroidDatabase, private val packageManager: PackageManager, ) { @@ -17,6 +18,7 @@ public class UpdateChecker( private val versionDao = db.getVersionDao() as VersionDaoInt private val appPrefsDao = db.getAppPrefsDao() as AppPrefsDaoInt private val compatibilityChecker = CompatibilityCheckerImpl(packageManager) + private val updateChecker = UpdateChecker(compatibilityChecker) /** * Returns a list of apps that can be updated. @@ -37,7 +39,7 @@ public class UpdateChecker( installedPackages.iterator().forEach { packageInfo -> val packageName = packageInfo.packageName val versions = versionsByPackage[packageName] ?: return@forEach // continue - val version = getVersion(versions, packageName, packageInfo, releaseChannels) + val version = getVersion(versions, packageName, packageInfo, null, releaseChannels) if (version != null) { val versionCode = packageInfo.getVersionCode() val app = getUpdatableApp(version, versionCode) @@ -48,13 +50,17 @@ public class UpdateChecker( } /** - * Returns an [AppVersion] for the given [packageName] that is an update + * Returns an [AppVersion] for the given [packageName] that is an update or new install * or null if there is none. * @param releaseChannels optional list of release channels to consider on top of stable. * If this is null or empty, only versions without channel (stable) will be considered. */ @SuppressLint("PackageManagerGetSignatures") - public fun getUpdate(packageName: String, releaseChannels: List? = null): AppVersion? { + public fun getSuggestedVersion( + packageName: String, + preferredSigner: String? = null, + releaseChannels: List? = null, + ): AppVersion? { val versions = versionDao.getVersions(listOf(packageName)) if (versions.isEmpty()) return null val packageInfo = try { @@ -63,7 +69,8 @@ public class UpdateChecker( } catch (e: PackageManager.NameNotFoundException) { null } - val version = getVersion(versions, packageName, packageInfo, releaseChannels) ?: return null + val version = getVersion(versions, packageName, packageInfo, preferredSigner, + releaseChannels) ?: return null val versionedStrings = versionDao.getVersionedStrings( repoId = version.repoId, packageId = version.packageId, @@ -76,50 +83,27 @@ public class UpdateChecker( versions: List, packageName: String, packageInfo: PackageInfo?, + preferredSigner: String?, releaseChannels: List?, ): Version? { - val versionCode = packageInfo?.getVersionCode() ?: 0 - // the below is rather expensive, so we only do that when there's update candidates - // TODO handle signingInfo.signingCertificateHistory as well - @Suppress("DEPRECATION") - val signatures by lazy { - packageInfo?.signatures?.map { - IndexUtils.getPackageSignature(it.toByteArray()) - }?.toSet() + val preferencesGetter: (() -> PackagePreference?) = { + appPrefsDao.getAppPrefsOrNull(packageName) } - val appPrefs by lazy { appPrefsDao.getAppPrefsOrNull(packageName) } - versions.iterator().forEach versions@{ version -> - // if the installed version has a known vulnerability, we return it as well - if (version.manifest.versionCode == versionCode && version.hasKnownVulnerability) { - return version - } - // if version code is not higher than installed skip package as list is sorted - if (version.manifest.versionCode <= versionCode) return null - // skip incompatible versions - if (!compatibilityChecker.isCompatible(version.manifest.toManifestV2())) return@versions - // only check release channels if they are not empty - if (!version.releaseChannels.isNullOrEmpty()) { - // add release channels from AppPrefs into the ones we allow - val channels = releaseChannels?.toMutableSet() ?: LinkedHashSet() - if (!appPrefs?.releaseChannels.isNullOrEmpty()) { - channels.addAll(appPrefs!!.releaseChannels) - } - // if allowed releases channels are empty (only stable) don't consider this version - if (channels.isEmpty()) return@versions - // don't consider version with non-matching release channel - if (channels.intersect(version.releaseChannels).isEmpty()) return@versions - } - val canInstall = if (packageInfo == null) { - true // take first one with highest version code and repo weight - } else { - // TODO also support AppPrefs with ignoring updates - val versionSignatures = version.manifest.signer?.sha256?.toSet() - signatures == versionSignatures - } - // no need to see other versions, we got the highest version code per sorting - if (canInstall) return version + return if (packageInfo == null) { + updateChecker.getSuggestedVersion( + versions = versions, + preferredSigner = preferredSigner, + releaseChannels = releaseChannels, + preferencesGetter = preferencesGetter, + ) + } else { + updateChecker.getUpdate( + versions = versions, + packageInfo = packageInfo, + releaseChannels = releaseChannels, + preferencesGetter = preferencesGetter, + ) } - return null } private fun getUpdatableApp(version: Version, installedVersionCode: Long): UpdatableApp? { diff --git a/database/src/main/java/org/fdroid/database/Version.kt b/database/src/main/java/org/fdroid/database/Version.kt index 45ca10d9a..7aaa85268 100644 --- a/database/src/main/java/org/fdroid/database/Version.kt +++ b/database/src/main/java/org/fdroid/database/Version.kt @@ -6,18 +6,18 @@ import androidx.room.Entity import androidx.room.ForeignKey import org.fdroid.database.VersionedStringType.PERMISSION import org.fdroid.database.VersionedStringType.PERMISSION_SDK_23 -import org.fdroid.index.v2.FeatureV2 +import org.fdroid.index.v2.ANTI_FEATURE_KNOWN_VULNERABILITY import org.fdroid.index.v2.FileV1 import org.fdroid.index.v2.FileV2 import org.fdroid.index.v2.LocalizedTextV2 import org.fdroid.index.v2.ManifestV2 +import org.fdroid.index.v2.PackageManifest +import org.fdroid.index.v2.PackageVersion import org.fdroid.index.v2.PackageVersionV2 import org.fdroid.index.v2.PermissionV2 -import org.fdroid.index.v2.SignatureV2 +import org.fdroid.index.v2.SignerV2 import org.fdroid.index.v2.UsesSdkV2 -private const val ANTI_FEATURE_KNOWN_VULNERABILITY = "KnownVuln" - @Entity( primaryKeys = ["repoId", "packageId", "versionId"], foreignKeys = [ForeignKey( @@ -35,12 +35,15 @@ public data class Version( @Embedded(prefix = "file_") val file: FileV1, @Embedded(prefix = "src_") val src: FileV2? = null, @Embedded(prefix = "manifest_") val manifest: AppManifest, - val releaseChannels: List? = emptyList(), + override val releaseChannels: List? = emptyList(), val antiFeatures: Map? = null, val whatsNew: LocalizedTextV2? = null, val isCompatible: Boolean, -) { - val hasKnownVulnerability: Boolean +) : PackageVersion { + override val versionCode: Long get() = manifest.versionCode + override val signer: SignerV2? get() = manifest.signer + override val packageManifest: PackageManifest get() = manifest + override val hasKnownVulnerability: Boolean get() = antiFeatures?.contains(ANTI_FEATURE_KNOWN_VULNERABILITY) == true internal fun toAppVersion(versionedStrings: List): AppVersion = AppVersion( @@ -97,20 +100,13 @@ public data class AppManifest( val versionName: String, val versionCode: Long, @Embedded(prefix = "usesSdk_") val usesSdk: UsesSdkV2? = null, - val maxSdkVersion: Int? = null, - @Embedded(prefix = "signer_") val signer: SignatureV2? = null, - val nativecode: List? = emptyList(), + override val maxSdkVersion: Int? = null, + @Embedded(prefix = "signer_") val signer: SignerV2? = null, + override val nativecode: List? = emptyList(), val features: List? = emptyList(), -) { - internal fun toManifestV2(): ManifestV2 = ManifestV2( - versionName = versionName, - versionCode = versionCode, - usesSdk = usesSdk, - maxSdkVersion = maxSdkVersion, - signer = signer, - nativecode = nativecode ?: emptyList(), - features = features?.map { FeatureV2(it) } ?: emptyList(), - ) +) : PackageManifest { + override val minSdkVersion: Int? get() = usesSdk?.minSdkVersion + override val featureNames: List? get() = features } internal fun ManifestV2.toManifest() = AppManifest( diff --git a/database/src/main/java/org/fdroid/database/VersionDao.kt b/database/src/main/java/org/fdroid/database/VersionDao.kt index de82509e7..e68501660 100644 --- a/database/src/main/java/org/fdroid/database/VersionDao.kt +++ b/database/src/main/java/org/fdroid/database/VersionDao.kt @@ -19,7 +19,7 @@ import org.fdroid.database.FDroidDatabaseHolder.dispatcher import org.fdroid.database.VersionedStringType.PERMISSION import org.fdroid.database.VersionedStringType.PERMISSION_SDK_23 import org.fdroid.index.IndexParser.json -import org.fdroid.index.v2.ManifestV2 +import org.fdroid.index.v2.PackageManifest import org.fdroid.index.v2.PackageVersionV2 import org.fdroid.index.v2.PermissionV2 import org.fdroid.index.v2.ReflectionDiffer @@ -88,7 +88,7 @@ internal interface VersionDaoInt : VersionDao { repoId: Long, packageId: String, versionsDiffMap: Map?, - checkIfCompatible: (ManifestV2) -> Boolean, + checkIfCompatible: (PackageManifest) -> Boolean, ) { if (versionsDiffMap == null) { // no more versions, delete all deleteAppVersion(repoId, packageId) @@ -100,7 +100,7 @@ internal interface VersionDaoInt : VersionDao { if (version == null) { // new version, parse normally val packageVersionV2: PackageVersionV2 = json.decodeFromJsonElement(jsonObject) - val isCompatible = checkIfCompatible(packageVersionV2.manifest) + val isCompatible = checkIfCompatible(packageVersionV2.packageManifest) insert(repoId, packageId, versionId, packageVersionV2, isCompatible) } else { // diff against existing version diffVersion(version, jsonObject, checkIfCompatible) @@ -112,7 +112,7 @@ internal interface VersionDaoInt : VersionDao { private fun diffVersion( version: Version, jsonObject: JsonObject, - checkIfCompatible: (ManifestV2) -> Boolean, + checkIfCompatible: (PackageManifest) -> Boolean, ) { // ensure that diff does not include internal keys DENY_LIST.forEach { forbiddenKey -> @@ -123,7 +123,7 @@ internal interface VersionDaoInt : VersionDao { } // diff version val diffedVersion = ReflectionDiffer.applyDiff(version, jsonObject) - val isCompatible = checkIfCompatible(diffedVersion.manifest.toManifestV2()) + val isCompatible = checkIfCompatible(diffedVersion.packageManifest) update(diffedVersion.copy(isCompatible = isCompatible)) // diff versioned strings val manifest = jsonObject["manifest"]