diff --git a/README.md b/README.md index 977b418..1aab546 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,9 @@ In the app: SHA-256, and open Android's installer. - If the same version APK was already downloaded and its SHA-256 still matches, the app reuses that file instead of downloading it again. +- 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 `More` -> `Open APK download` to download the current public APK in a browser. - Open the `Update` page or `More` -> `Permissions / update status` to see the diff --git a/app/src/main/java/com/neatstudio/tmuxandroid/MainActivity.java b/app/src/main/java/com/neatstudio/tmuxandroid/MainActivity.java index e1b61d1..33a6c4c 100644 --- a/app/src/main/java/com/neatstudio/tmuxandroid/MainActivity.java +++ b/app/src/main/java/com/neatstudio/tmuxandroid/MainActivity.java @@ -2019,6 +2019,14 @@ public final class MainActivity extends Activity { super.onBackPressed(); } + @Override + protected void onResume() { + super.onResume(); + if (updateManager != null) { + updateManager.resumePendingInstall(); + } + } + @Override protected void onDestroy() { closeTerminalSocket(); diff --git a/app/src/main/java/com/neatstudio/tmuxandroid/UpdateManager.java b/app/src/main/java/com/neatstudio/tmuxandroid/UpdateManager.java index 03bbd4d..7b724f6 100644 --- a/app/src/main/java/com/neatstudio/tmuxandroid/UpdateManager.java +++ b/app/src/main/java/com/neatstudio/tmuxandroid/UpdateManager.java @@ -25,6 +25,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; final class UpdateManager { + private static final String PREF_PENDING_INSTALL_APK = "pending_install_apk"; + interface Callback { void onChecking(boolean checking); void onMessage(String message); @@ -34,8 +36,9 @@ final class UpdateManager { private final SharedPreferences prefs; private final Callback callback; private final ExecutorService executor = Executors.newSingleThreadExecutor(); - private boolean checkInProgress; - private boolean downloadInProgress; + private volatile boolean checkInProgress; + private volatile boolean downloadInProgress; + private File pendingInstallApk; UpdateManager(Activity activity, SharedPreferences prefs, Callback callback) { this.activity = activity; @@ -226,15 +229,19 @@ final class UpdateManager { private void installApk(File apk) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !activity.getPackageManager().canRequestPackageInstalls()) { + pendingInstallApk = apk; + prefs.edit().putString(PREF_PENDING_INSTALL_APK, apk.getAbsolutePath()).apply(); Intent settingsIntent = new Intent( Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + activity.getPackageName()) ); activity.startActivity(settingsIntent); - Toast.makeText(activity, "Allow installs, then run update again", Toast.LENGTH_LONG).show(); + Toast.makeText(activity, "Allow installs, then return to continue", Toast.LENGTH_LONG).show(); postMessage("Install permission required"); return; } + pendingInstallApk = null; + prefs.edit().remove(PREF_PENDING_INSTALL_APK).apply(); Uri apkUri = UpdateFileProvider.getUriForFile( activity, @@ -253,6 +260,32 @@ final class UpdateManager { } } + void resumePendingInstall() { + File apk = pendingInstallApk; + if (apk == null) { + String path = prefs.getString(PREF_PENDING_INSTALL_APK, ""); + if (path != null && !path.isEmpty()) { + apk = new File(path); + pendingInstallApk = apk; + } + } + if (apk == null) { + return; + } + if (!apk.exists() || apk.length() <= 0) { + pendingInstallApk = null; + prefs.edit().remove(PREF_PENDING_INSTALL_APK).apply(); + postMessage("Downloaded APK is no longer available"); + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + && !activity.getPackageManager().canRequestPackageInstalls()) { + return; + } + postMessage("Continuing APK install..."); + installApk(apk); + } + private static byte[] readAllBytes(InputStream input) throws Exception { java.io.ByteArrayOutputStream output = new java.io.ByteArrayOutputStream(); byte[] buffer = new byte[16 * 1024]; diff --git a/docs/android-client-notes.md b/docs/android-client-notes.md index fac90dd..6d43e25 100644 --- a/docs/android-client-notes.md +++ b/docs/android-client-notes.md @@ -106,7 +106,9 @@ provider checks. Downloaded APKs are cached by `versionCode`. If a cached APK exists and its SHA-256 matches the manifest, the app reuses it instead of downloading the same version again. This matters when Android redirects the user to unknown-app -install settings before the installer can run. +install settings before the installer can run. After the user grants that +permission and returns to the app, the app resumes installation of the pending +APK instead of asking the user to run update again. Only `v*` tags publish GitHub Releases. Main branch builds are for CI artifacts and should be used to validate grouped changes. Do not publish a new tag for