diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36f7fcfeabe7..9b595b65d780 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2233,7 +2233,7 @@ jobs: uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: ~/.android-sdk - key: ${{ runner.os }}-android-sdk-v1-cmdline-14742923-platform-37.0-build-tools-36.0.0 + key: ${{ runner.os }}-android-sdk-v1-cmdline-14742923-platform-36-build-tools-36.0.0 restore-keys: | ${{ runner.os }}-android-sdk-v1- @@ -2263,7 +2263,7 @@ jobs: yes | sdkmanager --sdk_root="${ANDROID_SDK_ROOT}" --licenses >/dev/null sdkmanager --sdk_root="${ANDROID_SDK_ROOT}" --install \ "platform-tools" \ - "platforms;android-37.0" \ + "platforms;android-36" \ "build-tools;36.0.0" - name: Run Android ${{ matrix.task }} diff --git a/apps/android/app/build.gradle.kts b/apps/android/app/build.gradle.kts index 4dd7af5bea1d..70f6d800cb79 100644 --- a/apps/android/app/build.gradle.kts +++ b/apps/android/app/build.gradle.kts @@ -59,7 +59,7 @@ plugins { android { namespace = "ai.openclaw.app" - compileSdk = 37 + compileSdk = 36 // Release signing is local-only; keep the keystore path and passwords out of the repo. signingConfigs { diff --git a/apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewayDiscovery.kt b/apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewayDiscovery.kt index d57c103085c3..66807fc594be 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewayDiscovery.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewayDiscovery.kt @@ -49,18 +49,8 @@ import java.util.concurrent.Executors import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException -private fun createDnsResolver(context: Context): DnsResolver = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN) { - createContextDnsResolver(context) - } else { - createLegacyDnsResolver() - } - -@RequiresApi(Build.VERSION_CODES.CINNAMON_BUN) -private fun createContextDnsResolver(context: Context): DnsResolver = DnsResolver(context, null) - @Suppress("DEPRECATION") -private fun createLegacyDnsResolver(): DnsResolver = DnsResolver.getInstance() +private fun createDnsResolver(): DnsResolver = DnsResolver.getInstance() /** * Watches local DNS-SD and optional wide-area DNS-SD for reachable OpenClaw gateways. @@ -71,7 +61,7 @@ class GatewayDiscovery( ) { private val nsd = context.getSystemService(NsdManager::class.java) private val connectivity = context.getSystemService(ConnectivityManager::class.java) - private val dns = createDnsResolver(context) + private val dns = createDnsResolver() private val serviceType = "_openclaw-gw._tcp." private val wideAreaDomain = System.getenv("OPENCLAW_WIDE_AREA_DOMAIN") private val logTag = "OpenClaw/GatewayDiscovery" diff --git a/apps/android/benchmark/build.gradle.kts b/apps/android/benchmark/build.gradle.kts index 3bf47d195861..e3e2a6641bdc 100644 --- a/apps/android/benchmark/build.gradle.kts +++ b/apps/android/benchmark/build.gradle.kts @@ -5,7 +5,7 @@ plugins { android { namespace = "ai.openclaw.app.benchmark" - compileSdk = 37 + compileSdk = 36 defaultConfig { minSdk = 31 diff --git a/apps/android/gradle/libs.versions.toml b/apps/android/gradle/libs.versions.toml index 6700d68055d5..d4616a197617 100644 --- a/apps/android/gradle/libs.versions.toml +++ b/apps/android/gradle/libs.versions.toml @@ -4,7 +4,7 @@ androidx-activity = "1.13.0" androidx-benchmark = "1.4.1" androidx-camera = "1.6.0" androidx-compose-bom = "2026.05.01" -androidx-core = "1.19.0" +androidx-core = "1.18.0" androidx-exifinterface = "1.4.2" androidx-lifecycle = "2.10.0" androidx-security = "1.1.0" diff --git a/test/scripts/ci-workflow-guards.test.ts b/test/scripts/ci-workflow-guards.test.ts index f5ecb1c47d44..d10971b6e0b3 100644 --- a/test/scripts/ci-workflow-guards.test.ts +++ b/test/scripts/ci-workflow-guards.test.ts @@ -23,6 +23,14 @@ function readCriticalQualityWorkflow() { return readFileSync(".github/workflows/codeql-critical-quality.yml", "utf8"); } +function readAndroidCompileSdk(path: string): number { + const match = readFileSync(path, "utf8").match(/^\s*compileSdk\s*=\s*(\d+)\s*$/mu); + if (!match) { + throw new Error(`Missing compileSdk in ${path}`); + } + return Number(match[1]); +} + function findYamlFiles(directory: string): string[] { return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { const path = `${directory}/${entry.name}`; @@ -134,6 +142,22 @@ describe("ci workflow guards", () => { expect(workflow.jobs.android.strategy["max-parallel"]).toBe(2); }); + it("installs the Android SDK platform used by Gradle", () => { + const workflow = readCiWorkflow(); + const appCompileSdk = readAndroidCompileSdk("apps/android/app/build.gradle.kts"); + const benchmarkCompileSdk = readAndroidCompileSdk("apps/android/benchmark/build.gradle.kts"); + const cacheStep = workflow.jobs.android.steps.find((step) => step.name === "Cache Android SDK"); + const installStep = workflow.jobs.android.steps.find( + (step) => step.name === "Install Android SDK packages", + ); + const packageId = `platforms;android-${appCompileSdk}`; + + expect(appCompileSdk).toBe(benchmarkCompileSdk); + expect(cacheStep.with.key).toContain(`platform-${appCompileSdk}-`); + expect(installStep.run).toContain(`"${packageId}"`); + expect(installStep.run).not.toContain(`${packageId}.0`); + }); + it("debounces canonical main pushes before Blacksmith admission", () => { const workflow = readCiWorkflow(); const source = readFileSync(".github/workflows/ci.yml", "utf8");