diff --git a/README.md b/README.md index 91a98b8..81a2a89 100644 --- a/README.md +++ b/README.md @@ -177,8 +177,8 @@ In the app: - If Android sends you to the unknown-app install permission screen, return to the app after allowing it; the app continues installing the already downloaded APK without another update check or download. -- Open the `Update` page and tap `APK` to download the current public APK in a - browser. +- Open the `Update` page and tap `APK` to resolve the APK from the selected + update source and open it in a browser. - Open the `Update` page and tap `Details` to see the installed version, selected update source, APK URL, install permission state, and a `Check update` button. diff --git a/app/src/main/java/com/neatstudio/tmuxandroid/MainActivity.java b/app/src/main/java/com/neatstudio/tmuxandroid/MainActivity.java index 0528724..ea63bcf 100644 --- a/app/src/main/java/com/neatstudio/tmuxandroid/MainActivity.java +++ b/app/src/main/java/com/neatstudio/tmuxandroid/MainActivity.java @@ -3,7 +3,6 @@ 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; @@ -296,7 +295,7 @@ public final class MainActivity extends Activity { content.addView(actionPanel( actionButton("Check now", view -> updateManager.check(true)), actionButton("Source", view -> showUpdateSourcePicker()), - actionButton("APK", view -> openExternalUrl(BuildConfig.DEFAULT_APK_URL)) + actionButton("APK", view -> updateManager.openApkDownload()) )); content.addView(sectionTitle("Permissions")); content.addView(infoBlock("Android", permissionSummary())); @@ -351,7 +350,7 @@ public final class MainActivity extends Activity { "The app checks only the selected update source. APK downloads are cached by version and reused after Android install permission is granted." )); content.addView(actionPanel( - actionButton("Release page", view -> openExternalUrl(BuildConfig.DEFAULT_RELEASE_PAGE_URL)), + actionButton("Release page", view -> updateManager.openReleasePage()), actionButton("Update source", view -> showUpdateSourcePicker()), actionButton("Permissions", view -> showPermissionsAndUpdateStatus()) )); @@ -429,7 +428,7 @@ public final class MainActivity extends Activity { return; } if (PAGE_ABOUT.equals(activeMainPage)) { - actionRow.addView(toolbarButton("Release", view -> openExternalUrl(BuildConfig.DEFAULT_RELEASE_PAGE_URL))); + actionRow.addView(toolbarButton("Release", view -> updateManager.openReleasePage())); actionRow.addView(toolbarButton("Permissions", view -> showPermissionsAndUpdateStatus())); } } @@ -1181,12 +1180,10 @@ public final class MainActivity extends Activity { .append(")\n"); text.append("Server: ").append(getServerUrl()).append('\n'); 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("Manual APK download:\n"); + text.append("Tap APK on the Update page. The app resolves the APK from the selected update source.\n"); + text.append("Release page:\n"); + text.append("Tap Release on the About page. The app resolves the page from the selected update source.\n"); text.append('\n'); text.append("In-app update:\n"); text.append("Tap Update. The app checks only the selected source, downloads one APK per version, verifies SHA-256, then opens Android's installer.\n"); @@ -1229,14 +1226,6 @@ public final class MainActivity extends Activity { return text.toString(); } - 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, diff --git a/app/src/main/java/com/neatstudio/tmuxandroid/UpdateManager.java b/app/src/main/java/com/neatstudio/tmuxandroid/UpdateManager.java index 7b724f6..79c77b2 100644 --- a/app/src/main/java/com/neatstudio/tmuxandroid/UpdateManager.java +++ b/app/src/main/java/com/neatstudio/tmuxandroid/UpdateManager.java @@ -32,6 +32,10 @@ final class UpdateManager { void onMessage(String message); } + private interface ReleaseUrlPicker { + String pick(ReleaseInfo info); + } + private final Activity activity; private final SharedPreferences prefs; private final Callback callback; @@ -73,6 +77,40 @@ final class UpdateManager { }); } + void openApkDownload() { + openSelectedReleaseUrl("APK", info -> info.apkUrl); + } + + void openReleasePage() { + openSelectedReleaseUrl("Release page", info -> info.releasePageUrl); + } + + private void openSelectedReleaseUrl(String label, ReleaseUrlPicker picker) { + if (checkInProgress) { + postMessage("Update check already running"); + return; + } + String manifestUrl = getUpdateManifestUrl(); + checkInProgress = true; + callback.onChecking(true); + postMessage("Resolving " + label + " from " + hostLabel(manifestUrl) + "..."); + executor.execute(() -> { + try { + ReleaseInfo info = fetchReleaseInfo(manifestUrl); + String url = picker.pick(info); + if (url == null || url.trim().isEmpty()) { + throw new IllegalStateException(label + " URL is missing"); + } + activity.runOnUiThread(() -> openExternalUrl(url.trim(), label)); + } catch (Exception error) { + postMessage(label + " failed: " + error.getMessage()); + } finally { + checkInProgress = false; + activity.runOnUiThread(() -> callback.onChecking(false)); + } + }); + } + private String getUpdateManifestUrl() { String url = prefs.getString("update_url", BuildConfig.DEFAULT_UPDATE_URL); if (url == null || url.trim().isEmpty()) { @@ -260,6 +298,15 @@ final class UpdateManager { } } + private void openExternalUrl(String url, String label) { + try { + activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); + postMessage("Opened " + label); + } catch (ActivityNotFoundException error) { + postMessage("No app can open " + label); + } + } + void resumePendingInstall() { File apk = pendingInstallApk; if (apk == null) {