diff --git a/packages/client-android/android/.gradle/8.10.2/executionHistory/executionHistory.bin b/packages/client-android/android/.gradle/8.10.2/executionHistory/executionHistory.bin index 244c4b2..2675db3 100644 Binary files a/packages/client-android/android/.gradle/8.10.2/executionHistory/executionHistory.bin and b/packages/client-android/android/.gradle/8.10.2/executionHistory/executionHistory.bin differ diff --git a/packages/client-android/android/.gradle/8.10.2/executionHistory/executionHistory.lock b/packages/client-android/android/.gradle/8.10.2/executionHistory/executionHistory.lock index 78f074c..1ec14cf 100644 Binary files a/packages/client-android/android/.gradle/8.10.2/executionHistory/executionHistory.lock and b/packages/client-android/android/.gradle/8.10.2/executionHistory/executionHistory.lock differ diff --git a/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.bin b/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.bin index 253d375..81f68d4 100644 Binary files a/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.bin and b/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.bin differ diff --git a/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.lock b/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.lock index 76da75c..d108fdd 100644 Binary files a/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.lock and b/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.lock differ diff --git a/packages/client-android/android/.gradle/8.10.2/fileHashes/resourceHashesCache.bin b/packages/client-android/android/.gradle/8.10.2/fileHashes/resourceHashesCache.bin index 844cabd..3dc1a59 100644 Binary files a/packages/client-android/android/.gradle/8.10.2/fileHashes/resourceHashesCache.bin and b/packages/client-android/android/.gradle/8.10.2/fileHashes/resourceHashesCache.bin differ diff --git a/packages/client-android/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/packages/client-android/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock index 8739b5d..4be02ca 100644 Binary files a/packages/client-android/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/packages/client-android/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/packages/client-android/android/.gradle/file-system.probe b/packages/client-android/android/.gradle/file-system.probe index f2ab096..29e70e1 100644 Binary files a/packages/client-android/android/.gradle/file-system.probe and b/packages/client-android/android/.gradle/file-system.probe differ diff --git a/packages/client-android/android/app/build.gradle b/packages/client-android/android/app/build.gradle index 11694b6..eb7ace4 100644 --- a/packages/client-android/android/app/build.gradle +++ b/packages/client-android/android/app/build.gradle @@ -1,7 +1,7 @@ apply plugin: "com.android.application" apply plugin: "org.jetbrains.kotlin.android" -archivesBaseName = "gca-v0.1.0" +archivesBaseName = "v0.1.0-dav-android" android { namespace "dev.yuzu.gca" diff --git a/packages/client-android/android/app/src/main/java/dev/yuzu/gca/MainActivity.kt b/packages/client-android/android/app/src/main/java/dev/yuzu/gca/MainActivity.kt index de91edc..7941e5f 100644 --- a/packages/client-android/android/app/src/main/java/dev/yuzu/gca/MainActivity.kt +++ b/packages/client-android/android/app/src/main/java/dev/yuzu/gca/MainActivity.kt @@ -1,5 +1,6 @@ package dev.yuzu.gca +import android.graphics.Color import android.os.Bundle import android.widget.Button import android.widget.LinearLayout @@ -15,6 +16,7 @@ import java.security.MessageDigest class MainActivity : AppCompatActivity() { private lateinit var logView: TextView + private var selectedChannel = "stable" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -22,25 +24,61 @@ class MainActivity : AppCompatActivity() { val scroll = ScrollView(this) val layout = LinearLayout(this).apply { orientation = LinearLayout.VERTICAL - setPadding(32, 32, 32, 32) + setPadding(24, 24, 24, 24) } val title = TextView(this).apply { - text = "Yuzu GCA — OTA 测试" - textSize = 22f - setPadding(0, 0, 0, 16) + text = "Yuzu GCA v0.1.0" + textSize = 20f + setPadding(0, 0, 0, 8) } layout.addView(title) - logView = TextView(this).apply { + val channelLabel = TextView(this).apply { + text = "选择更新渠道:" textSize = 13f + setPadding(0, 8, 0, 4) + } + layout.addView(channelLabel) + + // 渠道按钮行 + val channelRow = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + setPadding(0, 0, 0, 12) + } + + fun makeChannelBtn(label: String, channel: String): Button { + return Button(this).apply { + text = label + textSize = 12f + setOnClickListener { + selectedChannel = channel + highlightChannel(channelRow, this) + } + } + } + + val btnStable = makeChannelBtn("Stable", "stable") + val btnBeta = makeChannelBtn("Beta", "beta") + val btnNightly = makeChannelBtn("Nightly", "nightly") + + channelRow.addView(btnStable) + channelRow.addView(btnBeta) + channelRow.addView(btnNightly) + layout.addView(channelRow) + highlightChannel(channelRow, btnStable) + + logView = TextView(this).apply { + textSize = 12f + setTextColor(Color.LTGRAY) } layout.addView(logView) val btn = Button(this).apply { - text = "检查更新并安装" + text = "检查更新" setOnClickListener { - CoroutineScope(Dispatchers.IO).launch { doOtaTest() } + val ch = selectedChannel + CoroutineScope(Dispatchers.IO).launch { doOtaTest(ch) } } } layout.addView(btn) @@ -48,74 +86,91 @@ class MainActivity : AppCompatActivity() { scroll.addView(layout) setContentView(scroll) - appendLog("设备已就绪\n点击按钮开始 OTA 端到端测试") + appendLog("渠道: stable | 点击[检查更新]开始") + } + + private fun highlightChannel(row: LinearLayout, active: Button) { + for (i in 0 until row.childCount) { + val b = row.getChildAt(i) as Button + b.isSelected = (b == active) + } } private fun appendLog(msg: String) { runOnUiThread { logView.append("$msg\n") } } - private suspend fun doOtaTest() = withContext(Dispatchers.IO) { - appendLog("=== OTA 端到端测试开始 ===") + private fun manifestUrl(channel: String): String { + val base = "https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/raw/branch/main/packages/client-android" + return when (channel) { + "beta" -> "$base/manifest-beta.json" + "nightly" -> "$base/manifest-nightly.json" + else -> "$base/manifest.json" + } + } + private suspend fun doOtaTest(channel: String) = withContext(Dispatchers.IO) { + val url = manifestUrl(channel) + appendLog("=== OTA 渠道: $channel ===") + appendLog("manifest: $url") + + // 1. 拉取 manifest appendLog("[1/4] 拉取 manifest...") + val manifestJson: String try { - val manifestJson = httpGet("https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/releases/download/v0.1.0-dav-android-6/manifest.json") - appendLog(" manifest: ${manifestJson.take(200)}...") + manifestJson = httpGet(url) + appendLog(" ✅ 获取成功 (${manifestJson.length}B)") } catch (e: Exception) { - appendLog(" ⚠️ 网络不可用: ${e.message}") - appendLog(" 代码逻辑已验证(版本解析/SHA256/PackageInstaller)") - appendLog("=== OTA 测试完成(离线模式) ===") + appendLog(" ❌ 获取失败: ${e.message}") + appendLog("=== 测试中止 ===") return@withContext } - appendLog("[2/4] 解析 manifest JSON...") + // 2. 解析 + appendLog("[2/4] 解析 manifest...") var manifestSha256 = "" var manifestApkUrl = "" + var manifestTag = "" try { - val manifestJson = httpGet("https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/releases/download/v0.1.0-dav-android-6/manifest.json") - // 简易 JSON 解析(不用 Gson,直接字符串提取) - val sha256Field = "\"sha256\": \"" - val sha256Start = manifestJson.indexOf(sha256Field) + sha256Field.length - val sha256End = manifestJson.indexOf("\"", sha256Start) - manifestSha256 = manifestJson.substring(sha256Start, sha256End) - - val urlField = "\"url\": \"" - val urlStart = manifestJson.indexOf(urlField) + urlField.length - val urlEnd = manifestJson.indexOf("\"", urlStart) - manifestApkUrl = manifestJson.substring(urlStart, urlEnd) - + fun extractJson(key: String): String { + val field = "\"$key\": \"" + val start = manifestJson.indexOf(field) + field.length + val end = manifestJson.indexOf("\"", start) + return manifestJson.substring(start, end) + } + manifestSha256 = extractJson("sha256") + manifestApkUrl = extractJson("url") + manifestTag = extractJson("tag") + appendLog(" 渠道: $channel 版本: $manifestTag") appendLog(" SHA256: ${manifestSha256.take(16)}...") - appendLog(" APK URL: ${manifestApkUrl.take(50)}...") - appendLog(" 需要更新 ✅") } catch (e: Exception) { - appendLog(" ⚠️ manifest 解析失败: ${e.message}") - appendLog("=== OTA 测试中止 ===") + appendLog(" ❌ 解析失败: ${e.message}") return@withContext } - appendLog("[3/4] 下载 APK: ${manifestApkUrl.take(40)}...") - val apkFile = File(cacheDir, "test-update.apk") + // 3. 下载 + appendLog("[3/4] 下载 APK...") + val apkFile = File(cacheDir, "update-$channel.apk") try { downloadFile(manifestApkUrl, apkFile) - appendLog(" 下载完成: ${apkFile.length()} bytes ✅") + appendLog(" ✅ 下载完成: ${apkFile.length()} bytes") } catch (e: Exception) { appendLog(" ❌ 下载失败: ${e.message}") return@withContext } + // 4. SHA256 校验 appendLog("[4/4] SHA256 校验...") val actualSha256 = computeSha256(apkFile) if (actualSha256 == manifestSha256) { appendLog(" ✅ SHA256 校验通过!") - appendLog(" ${actualSha256.take(32)}...") } else { appendLog(" ❌ SHA256 校验失败!") - appendLog(" 期望: ${manifestSha256.take(32)}...") - appendLog(" 实际: ${actualSha256.take(32)}...") + appendLog(" expected: ${manifestSha256.take(32)}") + appendLog(" actual: ${actualSha256.take(32)}") } - appendLog("=== OTA 测试完成 ===") + appendLog("=== $channel 渠道测试完成 ===") } private fun httpGet(url: String): String { diff --git a/packages/client-android/manifest-beta.json b/packages/client-android/manifest-beta.json new file mode 100644 index 0000000..b238a9a --- /dev/null +++ b/packages/client-android/manifest-beta.json @@ -0,0 +1 @@ +{"version":"0.1.0","builds":{"android":{"type":"apk","tag":"v0.1.0-dav-android-7-beta","url":"https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/releases/download/v0.1.0-dav-android-7/v0.1.0-dav-android-debug.apk","sha256":"6cd6bbfe07e8b86b06bc328b7b08138dfb00defc71c8bcc98fbb256b2153aba6","size":3223411,"channel":"beta"}}} \ No newline at end of file diff --git a/packages/client-android/manifest-nightly.json b/packages/client-android/manifest-nightly.json new file mode 100644 index 0000000..a6dd7c4 --- /dev/null +++ b/packages/client-android/manifest-nightly.json @@ -0,0 +1 @@ +{"version":"0.1.0","builds":{"android":{"type":"apk","tag":"v0.1.0-dav-android-7-nightly","url":"https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/releases/download/v0.1.0-dav-android-7/v0.1.0-dav-android-debug.apk","sha256":"6cd6bbfe07e8b86b06bc328b7b08138dfb00defc71c8bcc98fbb256b2153aba6","size":3223411,"channel":"nightly"}}} \ No newline at end of file diff --git a/packages/client-android/manifest.json b/packages/client-android/manifest.json index 1c24a26..a6f38e8 100644 --- a/packages/client-android/manifest.json +++ b/packages/client-android/manifest.json @@ -3,10 +3,11 @@ "builds": { "android": { "type": "apk", - "tag": "v0.1.0-dav-android-6", - "url": "https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/releases/download/v0.1.0-dav-android-6/gca-v0.1.0.apk", + "tag": "v0.1.0-dav-android-7", + "url": "https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/releases/download/v0.1.0-dav-android-7/v0.1.0-dav-android-debug.apk", "sha256": "6cd6bbfe07e8b86b06bc328b7b08138dfb00defc71c8bcc98fbb256b2153aba6", "size": 3223411, + "channel": "stable", "minVersion": "0.1.0" } }