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