Resume pending APK install
Some checks failed
Gitea Android APK / build (push) Failing after 1s
Gitea Smoke / smoke (push) Successful in 0s

This commit is contained in:
Codex 2026-07-06 12:38:44 +00:00
parent fc1cf589a7
commit 67968654cb
4 changed files with 50 additions and 4 deletions

View File

@ -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

View File

@ -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();

View File

@ -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];

View File

@ -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