ae29f38f6b
ServiceState serializes start, stop, and restart intents so the latest request wins, and switching between VPN and proxy mode tears the previous service down the way stop() does before binding the next one, so a system-started VpnService no longer outlives the switch. The package resolver drops its cache on package add, replace, and remove broadcasts and notifies Flutter, and the persistent notification can omit its stop action through an application setting synced over the shared state. The :core module takes the NDK clang directory and API level from the setup build hook input instead of ANDROID_NDK and a hardcoded API 21, and derives its ABI list from the target-platform property so a single-ABI build no longer links ABIs the hook did not produce. Changelog: Rework the Android VPN service and lifecycle handling
88 lines
2.8 KiB
Kotlin
88 lines
2.8 KiB
Kotlin
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
|
|
|
plugins {
|
|
id("com.android.library")
|
|
}
|
|
|
|
// Must equal Target.forPlatform("android") in setup_hooks, which writes libclash.so only for the platforms being built.
|
|
val coreAbiByPlatform =
|
|
linkedMapOf("android-arm" to "armeabi-v7a", "android-arm64" to "arm64-v8a", "android-x64" to "x86_64")
|
|
val coreAbis =
|
|
(rootProject.findProperty("target-platform") as String?)
|
|
?.split(",")
|
|
?.map { coreAbiByPlatform[it.trim()] ?: throw GradleException("No Core for Flutter target platform $it") }
|
|
?: coreAbiByPlatform.values.toList()
|
|
|
|
android {
|
|
namespace = "com.follow.clash.core"
|
|
compileSdk = libs.versions.compileSdk.get().toInt()
|
|
ndkVersion = libs.versions.ndkVersion.get()
|
|
|
|
defaultConfig {
|
|
minSdk = libs.versions.minSdk.get().toInt()
|
|
|
|
ndk {
|
|
abiFilters += coreAbis
|
|
}
|
|
}
|
|
|
|
externalNativeBuild {
|
|
cmake {
|
|
path("src/main/cpp/CMakeLists.txt")
|
|
version = "3.22.1"
|
|
}
|
|
}
|
|
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_17
|
|
targetCompatibility = JavaVersion.VERSION_17
|
|
}
|
|
|
|
}
|
|
|
|
kotlin {
|
|
compilerOptions {
|
|
jvmTarget.set(JvmTarget.JVM_17)
|
|
}
|
|
}
|
|
|
|
dependencies {
|
|
implementation(libs.annotation.jvm)
|
|
}
|
|
|
|
// Why prefix matching and mustRunAfter: .agents/architecture.md, "Android Native Task Ordering".
|
|
val nativeTaskPattern =
|
|
Regex("^(configureCMake|buildCMake|externalNativeBuild|merge.*(NativeLibs|JniLibFolders)|copy.*JniLibs)")
|
|
val flutterCompileTasks =
|
|
rootProject.project(":app").tasks.matching { it.name.startsWith("compileFlutterBuild") }
|
|
|
|
tasks.matching { nativeTaskPattern.containsMatchIn(it.name) }.configureEach {
|
|
mustRunAfter(flutterCompileTasks)
|
|
}
|
|
|
|
gradle.taskGraph.whenReady {
|
|
val nativeTask =
|
|
allTasks.firstOrNull {
|
|
it.path.startsWith("${project.path}:") && nativeTaskPattern.containsMatchIn(it.name)
|
|
}
|
|
if (nativeTask == null || allTasks.any { it.path.startsWith(":app:compileFlutterBuild") }) {
|
|
return@whenReady
|
|
}
|
|
val missing =
|
|
coreAbis
|
|
.flatMap {
|
|
listOf(file("src/main/jniLibs/$it/libclash.so"), file("src/main/cpp/includes/$it/libclash.h"))
|
|
}.filterNot { it.isFile }
|
|
if (missing.isNotEmpty()) {
|
|
throw GradleException(
|
|
"${nativeTask.name} consumes Core artifacts that the setup build hook writes into src/main, " +
|
|
"and these are absent: ${missing.joinToString { it.relativeTo(projectDir).path }}. " +
|
|
"Run `dart setup.dart android` once, or build through :app so the hook runs first.",
|
|
)
|
|
}
|
|
logger.warn(
|
|
":core: ${nativeTask.name} is running without an :app Flutter compile task, " +
|
|
"so the Core artifacts under src/main are whatever the last hook run left behind",
|
|
)
|
|
}
|