diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BlockingUpdateView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BlockingUpdateView.java index 2983daec..c42f9c3e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BlockingUpdateView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BlockingUpdateView.java @@ -149,7 +149,11 @@ public class BlockingUpdateView extends FrameLayout implements NotificationCente showProgress(true); } } else if (appUpdate.url != null) { - Browser.openUrl(getContext(), appUpdate.url); + if (getContext() instanceof Activity activity) { + ApkInstaller.downloadAndInstall(activity, appUpdate.url, appUpdate.version); + } else { + Browser.openUrl(getContext(), appUpdate.url); + } } }); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/UpdateAppAlertDialog.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/UpdateAppAlertDialog.java index b2ecd56e..7df6df7d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/UpdateAppAlertDialog.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/UpdateAppAlertDialog.java @@ -4,6 +4,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; @@ -38,6 +39,8 @@ import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.spoilers.SpoilersTextView; import org.telegram.ui.Stories.recorder.ButtonWithCounterView; +import tw.nekomimi.nekogram.helpers.ApkInstaller; + public class UpdateAppAlertDialog extends BottomSheet { private TLRPC.TL_help_appUpdate appUpdate; @@ -297,8 +300,12 @@ public class UpdateAppAlertDialog extends BottomSheet { doneButton.setOnClickListener(v -> { if (update.document instanceof TLRPC.TL_document) { FileLoader.getInstance(accountNum).loadFile(appUpdate.document, "update", FileLoader.PRIORITY_NORMAL, 1); - } else { - Browser.openUrl(context, appUpdate.url); + } else if (appUpdate.url != null) { + if (getContext() instanceof Activity activity) { + ApkInstaller.downloadAndInstall(activity, appUpdate.url, appUpdate.version); + } else { + Browser.openUrl(context, appUpdate.url); + } } dismiss(); }); diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/ApkInstaller.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/ApkInstaller.java index f0fac44e..8533eb6c 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/ApkInstaller.java +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/ApkInstaller.java @@ -34,6 +34,7 @@ import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; import org.telegram.messenger.Utilities; import org.telegram.messenger.XiaomiUtilities; +import org.telegram.messenger.browser.Browser; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.Theme; @@ -44,9 +45,12 @@ import org.telegram.ui.LaunchActivity; import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -107,6 +111,113 @@ public final class ApkInstaller { if (apk == null) { return; } + showInstallDialog(context, apk); + } + + // Downloads an APK from a direct URL (e.g. a GitHub release asset) with a + // progress dialog, then performs the same silent install as the Telegram + // document update flow. + public static void downloadAndInstall(Activity context, String url, String version) { + if (context == null || TextUtils.isEmpty(url)) { + return; + } + // Not a direct apk link (e.g. a release page) — just open it. + if (!url.toLowerCase().endsWith(".apk")) { + Browser.openUrl(context, url); + return; + } + if (dialog != null && dialog.isShowing()) { + return; + } + var progressDialog = new AlertDialog(context, AlertDialog.ALERT_TYPE_LOADING); + progressDialog.setMessage(LocaleController.getString(R.string.UpdateDownloading)); + progressDialog.setCanCancel(false); + progressDialog.show(); + dialog = progressDialog; + + Utilities.globalQueue.postRunnable(() -> { + File apk = downloadApk(context, url, version, progress -> AndroidUtilities.runOnUIThread(() -> { + if (dialog != null && dialog.isShowing()) { + dialog.setProgress(progress); + } + })); + AndroidUtilities.runOnUIThread(() -> { + if (dialog != null) { + dialog.dismiss(); + dialog = null; + } + if (apk == null) { + AlertsCreator.createSimpleAlert(context, LocaleController.getString(R.string.ErrorOccurred)).show(); + return; + } + showInstallDialog(context, apk); + }); + }); + } + + // @WorkerThread @Nullable + private static File downloadApk(Activity context, String url, String version, Utilities.Callback onProgress) { + HttpURLConnection connection = null; + try { + File dir = FileLoader.getDirectory(FileLoader.MEDIA_DIR_CACHE); + String name = "update-" + (TextUtils.isEmpty(version) ? "latest" : version.replaceAll("[^A-Za-z0-9._-]", "_")) + ".apk"; + File apk = new File(dir, name); + if (apk.exists()) { + //noinspection ResultOfMethodCallIgnored + apk.delete(); + } + + URL downloadUrl = new URL(url); + connection = (HttpURLConnection) downloadUrl.openConnection(); + connection.setInstanceFollowRedirects(true); + connection.setConnectTimeout(15000); + connection.setReadTimeout(30000); + connection.setRequestProperty("User-Agent", ApplicationLoader.getApplicationId()); + + int code = connection.getResponseCode(); + if (code != HttpURLConnection.HTTP_OK) { + FileLog.e("ApkInstaller: download failed, HTTP " + code); + return null; + } + + long total = connection.getContentLength(); + long downloaded = 0; + int lastReported = -1; + try (InputStream in = connection.getInputStream(); + OutputStream out = new FileOutputStream(apk)) { + byte[] buffer = new byte[8192]; + int read; + while ((read = in.read(buffer)) >= 0) { + out.write(buffer, 0, read); + downloaded += read; + if (total > 0 && onProgress != null) { + int progress = (int) (downloaded * 100 / total); + if (progress != lastReported) { + lastReported = progress; + onProgress.run(progress); + } + } + } + } + return apk; + } catch (Exception e) { + FileLog.e(e); + return null; + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + + private static void showInstallDialog(Activity context, File apk) { + if (context == null || apk == null || !apk.exists()) { + return; + } + if (hasBrokenPackageInstaller(context)) { + AndroidUtilities.openForView(apk, "install.apk", "application/vnd.android.package-archive", context, null, false); + return; + } if (dialog != null && dialog.isShowing()) { return; } diff --git a/TMessagesProj/src/main/res/values/strings_neko.xml b/TMessagesProj/src/main/res/values/strings_neko.xml index e7a9152e..943996ca 100644 --- a/TMessagesProj/src/main/res/values/strings_neko.xml +++ b/TMessagesProj/src/main/res/values/strings_neko.xml @@ -149,6 +149,7 @@ Share FoxiGram... FoxiGram %1$s\nBased on Telegram %2$s\nDesigned by %3$s Installing update... + Downloading update... A notification will be shown when the update completes. The app will relaunch when the update completes. Update installation finished, tap to launch the app.