diff --git a/app/src/main/java/org/fdroid/fdroid/CompatibilityChecker.java b/app/src/main/java/org/fdroid/fdroid/CompatibilityChecker.java index 5ac6bcd6d..1724abc51 100644 --- a/app/src/main/java/org/fdroid/fdroid/CompatibilityChecker.java +++ b/app/src/main/java/org/fdroid/fdroid/CompatibilityChecker.java @@ -7,6 +7,7 @@ import android.os.Build; import androidx.annotation.Nullable; +import org.fdroid.CompatibilityCheckerUtils; import org.fdroid.fdroid.data.Apk; import java.util.ArrayList; @@ -76,6 +77,13 @@ public class CompatibilityChecker { Utils.getAndroidVersionName(apk.maxSdkVersion))); } + int minInstallableTargetSdk = CompatibilityCheckerUtils.INSTANCE.minInstallableTargetSdk(); + if (apk.targetSdkVersion < minInstallableTargetSdk) { + incompatibleReasons.add(context.getString( + R.string.targetsdk_or_later, + Utils.getAndroidVersionName(minInstallableTargetSdk))); + } + if (apk.features != null) { for (final String feat : apk.features) { if (forceTouchApps && "android.hardware.touchscreen".equals(feat)) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9dfaecc7c..e3aec0943 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -467,6 +467,7 @@ This often occurs with apps installed via Google Play or other sources, if they this network: %s Requires: %1$s + targets %s Language System Default diff --git a/libs/database/src/main/java/org/fdroid/database/Version.kt b/libs/database/src/main/java/org/fdroid/database/Version.kt index 3b1227572..f72459b27 100644 --- a/libs/database/src/main/java/org/fdroid/database/Version.kt +++ b/libs/database/src/main/java/org/fdroid/database/Version.kt @@ -134,6 +134,7 @@ public data class AppManifest( ) : PackageManifest { public override val minSdkVersion: Int? get() = usesSdk?.minSdkVersion public override val featureNames: List? get() = features + public override val targetSdkVersion: Int? get() = usesSdk?.targetSdkVersion } internal fun ManifestV2.toManifest() = AppManifest( diff --git a/libs/index/src/androidMain/kotlin/org/fdroid/CompatibilityChecker.kt b/libs/index/src/androidMain/kotlin/org/fdroid/CompatibilityChecker.kt index 5d4ee694c..17a9b3965 100644 --- a/libs/index/src/androidMain/kotlin/org/fdroid/CompatibilityChecker.kt +++ b/libs/index/src/androidMain/kotlin/org/fdroid/CompatibilityChecker.kt @@ -30,6 +30,8 @@ public class CompatibilityCheckerImpl @JvmOverloads constructor( public override fun isCompatible(manifest: PackageManifest): Boolean { if (sdkInt < manifest.minSdkVersion ?: 0) return false if (sdkInt > manifest.maxSdkVersion ?: Int.MAX_VALUE) return false + if ((manifest.targetSdkVersion ?: 1) < + CompatibilityCheckerUtils.minInstallableTargetSdk(sdkInt)) return false if (!isNativeCodeCompatible(manifest)) return false manifest.featureNames?.iterator()?.forEach { feature -> if (forceTouchApps && feature == "android.hardware.touchscreen") return@forEach @@ -47,3 +49,19 @@ public class CompatibilityCheckerImpl @JvmOverloads constructor( return false } } + +/** + * Contains helper methods for checking compatibility of an APK + */ +public object CompatibilityCheckerUtils { + // Mirrored from AOSP due to lack of public APIs + // frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java + // TODO: Keep this in sync with AOSP to avoid INSTALL_FAILED_DEPRECATED_SDK_VERSION errors + @JvmOverloads + public fun minInstallableTargetSdk(sdkInt: Int = SDK_INT): Int { + return when (sdkInt) { + 34 -> 23 // Android 6.0, M + else -> 1 // Android 1.0, BASE + } + } +} diff --git a/libs/index/src/androidUnitTest/kotlin/org/fdroid/CompatibilityCheckerTest.kt b/libs/index/src/androidUnitTest/kotlin/org/fdroid/CompatibilityCheckerTest.kt index 9174f4c97..6db131619 100644 --- a/libs/index/src/androidUnitTest/kotlin/org/fdroid/CompatibilityCheckerTest.kt +++ b/libs/index/src/androidUnitTest/kotlin/org/fdroid/CompatibilityCheckerTest.kt @@ -103,11 +103,34 @@ internal class CompatibilityCheckerTest { assertFalse(checker.isCompatible(manifest4)) } + @Test + fun targetSdkIsRespected() { + // greater or equal than minInstallableTargetSdk are compatible + val manifest1 = Manifest(targetSdkVersion = Int.MAX_VALUE) + assertTrue(checker.isCompatible(manifest1)) + val manifest2 = Manifest(targetSdkVersion = 23) + val checker2 = CompatibilityCheckerImpl( + packageManager = packageManager, + sdkInt = 34, + supportedAbis = emptyArray() + ) + assertTrue(checker2.isCompatible(manifest2)) + // a targetSdk smaller than minInstallableTargetSdk is not compatible + val manifest3 = Manifest(targetSdkVersion = 22) + val checker3 = CompatibilityCheckerImpl( + packageManager = packageManager, + sdkInt = 34, + supportedAbis = emptyArray() + ) + assertFalse(checker3.isCompatible(manifest3)) + } + private data class Manifest( override val minSdkVersion: Int? = null, override val maxSdkVersion: Int? = null, override val featureNames: List? = null, override val nativecode: List? = null, + override val targetSdkVersion: Int? = null, ) : PackageManifest } diff --git a/libs/index/src/androidUnitTest/kotlin/org/fdroid/UpdateCheckerTest.kt b/libs/index/src/androidUnitTest/kotlin/org/fdroid/UpdateCheckerTest.kt index 3d77afdb5..8a6ba5697 100644 --- a/libs/index/src/androidUnitTest/kotlin/org/fdroid/UpdateCheckerTest.kt +++ b/libs/index/src/androidUnitTest/kotlin/org/fdroid/UpdateCheckerTest.kt @@ -189,6 +189,7 @@ internal class UpdateCheckerTest { override val maxSdkVersion: Int? = null override val featureNames: List? = null override val nativecode: List? = null + override val targetSdkVersion: Int? = null }, override val hasKnownVulnerability: Boolean = false, ) : PackageVersion diff --git a/libs/index/src/commonMain/kotlin/org/fdroid/index/v2/PackageV2.kt b/libs/index/src/commonMain/kotlin/org/fdroid/index/v2/PackageV2.kt index 43009a818..aba140323 100644 --- a/libs/index/src/commonMain/kotlin/org/fdroid/index/v2/PackageV2.kt +++ b/libs/index/src/commonMain/kotlin/org/fdroid/index/v2/PackageV2.kt @@ -137,6 +137,7 @@ public interface PackageManifest { public val maxSdkVersion: Int? public val featureNames: List? public val nativecode: List? + public val targetSdkVersion: Int? } @Serializable @@ -153,6 +154,7 @@ public data class ManifestV2( ) : PackageManifest { override val minSdkVersion: Int? = usesSdk?.minSdkVersion override val featureNames: List = features.map { it.name } + override val targetSdkVersion: Int? = usesSdk?.targetSdkVersion } @Serializable