diff --git a/README.md b/README.md index 3f1fc49..f7a09e7 100644 --- a/README.md +++ b/README.md @@ -77,10 +77,13 @@ Publish a test build by pushing a `v*` tag. That creates a GitHub Release with: ```text https://github.com/neatstudio/tmux-browser-android/releases/latest/download/tmux-android.apk https://github.com/neatstudio/tmux-browser-android/releases/latest/download/latest.json -https://gitea.neatcn.com/tmux/tmux-browser-android/releases/latest/download/tmux-android.apk -https://gitea.neatcn.com/tmux/tmux-browser-android/releases/latest/download/latest.json ``` +Those GitHub links are the stable public install/update channel. Gitea releases +are mirrored for internal tracking, but the current Gitea org/repo visibility +keeps anonymous release downloads behind login, so phones should use GitHub for +no-login install and in-app updates. + Plain branch builds only create Actions artifacts; they are useful for CI verification, but releases are the stable download/update channel. @@ -126,13 +129,26 @@ The default update manifest is: https://github.com/neatstudio/tmux-browser-android/releases/latest/download/latest.json ``` -The Gitea mirror is: +The public manual APK download is: ```text -https://gitea.neatcn.com/tmux/tmux-browser-android/releases/latest/download/latest.json +https://github.com/neatstudio/tmux-browser-android/releases/latest/download/tmux-android.apk ``` The app tries the selected update source first, then falls back to the GitHub -and Gitea mirrors. +manifest and the Gitea release API. GitHub is the reliable no-login source today. +The Gitea API endpoint is: -The workflow uploads both the APK and `latest.json` to each release. +```text +https://gitea.neatcn.com/api/v1/repos/tmux/tmux-browser-android/releases/latest +``` + +In the app: + +- Tap `Update` on the main screen to check `latest.json`, download the APK, + verify SHA-256, and open Android's installer. +- Open `More` -> `Open APK download` to download the current public APK in a + browser. +- Open `More` -> `Permissions / update status` to see the installed version, + update manifest, APK URL, install permission state, and a `Check update` + button. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0d7db5a..f97c576 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -12,6 +12,10 @@ val defaultUpdateUrl = providers.gradleProperty("defaultUpdateUrl") .orElse("https://github.com/${repoSlug.get()}/releases/latest/download/latest.json") val defaultGiteaUpdateUrl = providers.gradleProperty("defaultGiteaUpdateUrl") .orElse("https://gitea.neatcn.com/api/v1/repos/tmux/tmux-browser-android/releases/latest") +val defaultApkUrl = providers.gradleProperty("defaultApkUrl") + .orElse("https://github.com/${repoSlug.get()}/releases/latest/download/tmux-android.apk") +val defaultReleasePageUrl = providers.gradleProperty("defaultReleasePageUrl") + .orElse("https://github.com/${repoSlug.get()}/releases/latest") val signingProps = Properties() val signingFile = rootProject.file("signing.properties") @@ -33,6 +37,8 @@ android { buildConfigField("String", "DEFAULT_SERVER_URL", "\"${defaultServerUrl.get()}\"") buildConfigField("String", "DEFAULT_UPDATE_URL", "\"${defaultUpdateUrl.get()}\"") buildConfigField("String", "DEFAULT_GITEA_UPDATE_URL", "\"${defaultGiteaUpdateUrl.get()}\"") + buildConfigField("String", "DEFAULT_APK_URL", "\"${defaultApkUrl.get()}\"") + buildConfigField("String", "DEFAULT_RELEASE_PAGE_URL", "\"${defaultReleasePageUrl.get()}\"") } buildFeatures { diff --git a/app/src/main/java/com/neatstudio/tmuxandroid/MainActivity.java b/app/src/main/java/com/neatstudio/tmuxandroid/MainActivity.java index 7d69fe7..eb0904c 100644 --- a/app/src/main/java/com/neatstudio/tmuxandroid/MainActivity.java +++ b/app/src/main/java/com/neatstudio/tmuxandroid/MainActivity.java @@ -3,6 +3,7 @@ package com.neatstudio.tmuxandroid; import android.Manifest; import android.app.Activity; import android.app.AlertDialog; +import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; @@ -463,6 +464,7 @@ public final class MainActivity extends Activity { "Upload image URL", "Image preview info", "Open image preview", + "Open APK download", "Update source", "Permissions / update status" }; @@ -525,9 +527,12 @@ public final class MainActivity extends Activity { promptOpenImagePreview(); break; case 18: - showUpdateSourcePicker(); + openExternalUrl(BuildConfig.DEFAULT_APK_URL); break; case 19: + showUpdateSourcePicker(); + break; + case 20: showPermissionsAndUpdateStatus(); break; default: @@ -914,12 +919,28 @@ public final class MainActivity extends Activity { private void showPermissionsAndUpdateStatus() { StringBuilder text = new StringBuilder(); + text.append("Installed app: ") + .append(BuildConfig.VERSION_NAME) + .append(" (") + .append(BuildConfig.VERSION_CODE) + .append(")\n"); text.append("Server: ").append(getServerUrl()).append('\n'); - text.append("Selected update manifest: ") + text.append('\n'); + text.append("Manual APK download:\n") + .append(BuildConfig.DEFAULT_APK_URL) + .append('\n'); + text.append("Release page:\n") + .append(BuildConfig.DEFAULT_RELEASE_PAGE_URL) + .append('\n'); + text.append('\n'); + text.append("In-app update:\n"); + text.append("Tap Update. The app reads latest.json, downloads the APK, verifies SHA-256, then opens Android's installer.\n"); + text.append("Selected manifest:\n") .append(prefs.getString("update_url", BuildConfig.DEFAULT_UPDATE_URL)) .append('\n'); - text.append("GitHub mirror: ").append(BuildConfig.DEFAULT_UPDATE_URL).append('\n'); - text.append("Gitea mirror: ").append(BuildConfig.DEFAULT_GITEA_UPDATE_URL).append('\n'); + text.append("GitHub manifest:\n").append(BuildConfig.DEFAULT_UPDATE_URL).append('\n'); + text.append("Gitea release API:\n").append(BuildConfig.DEFAULT_GITEA_UPDATE_URL).append('\n'); + text.append('\n'); text.append("Network: manifest permission, no runtime grant required\n"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { text.append("Install unknown apps: ") @@ -942,10 +963,18 @@ public final class MainActivity extends Activity { .setMessage(text.toString()) .setNegativeButton("Close", null) .setNeutralButton("Install permission", (dialog, which) -> openInstallPermissionSettings()) - .setPositiveButton("Notify permission", (dialog, which) -> requestNotificationPermission()) + .setPositiveButton("Check update", (dialog, which) -> updateManager.check(true)) .show(); } + private void openExternalUrl(String url) { + try { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); + } catch (ActivityNotFoundException error) { + showMessage("No app can open URL"); + } + } + private void showUpdateSourcePicker() { String[] items = { "GitHub: " + BuildConfig.DEFAULT_UPDATE_URL,