diff --git a/app/src/main/java/com/evsuite/abrp/MainActivity.java b/app/src/main/java/com/evsuite/abrp/MainActivity.java index 22bb3eb..6a136c9 100644 --- a/app/src/main/java/com/evsuite/abrp/MainActivity.java +++ b/app/src/main/java/com/evsuite/abrp/MainActivity.java @@ -41,7 +41,7 @@ public class MainActivity extends AppCompatActivity { private static final int LOCATION_PERMISSION_REQUEST = 100; /** Shared with {@link AbrpUploadService}, which reads it on every tick. */ static final String WIFI_AUTO_KEY = "wifi_auto_connect"; - private static final String REPOSITORY_URL = "https://github.com/fatihdonmezdev/EVABRP"; + private static final String REPOSITORY_URL = "https://github.com/fatihdonmezdev/MG4ABRP"; /** Top-bar tabs, in page order. Parallel to {@link #panes}. */ private static final int[] TAB_IDS = @@ -230,6 +230,7 @@ public class MainActivity extends AppCompatActivity { setUpPager(); findViewById(R.id.about_button).setOnClickListener(v -> showAbout()); + findViewById(R.id.update_button).setOnClickListener(v -> checkForUpdate(v)); bindCadenceControls(); @@ -629,6 +630,30 @@ public class MainActivity extends AppCompatActivity { AbrpUploadService.reloadSettings(); } + // ---------- Update check ---------- + + /** + * Checks for a newer build, and installs one if it is there. + * + * The button is disabled for the duration rather than guarded by a flag: the whole + * sequence — API call, download, {@code pm install} — runs on a background thread, and + * a second press would start a second download of the same APK. It comes back on when + * the result lands, whatever that result is. + * + * The outcome goes to a toast because there is nowhere better: the check is reachable + * from every page, so no one page owns the answer. + */ + private void checkForUpdate(View button) { + button.setEnabled(false); + android.widget.Toast.makeText(this, R.string.update_checking, + android.widget.Toast.LENGTH_SHORT).show(); + UpdateHook.checkInBackground(this, message -> { + button.setEnabled(true); + android.widget.Toast.makeText(this, message, + android.widget.Toast.LENGTH_LONG).show(); + }); + } + // ---------- Wi-Fi page ---------- private void bindWifiPane() { diff --git a/app/src/main/res/drawable/ic_refresh.xml b/app/src/main/res/drawable/ic_refresh.xml new file mode 100644 index 0000000..a4c3357 --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 98b0c16..f2b3d5d 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -92,8 +92,21 @@ android:text="@string/tab_log" app:icon="@drawable/ic_tab_log" /> - + + + Shows vehicle telemetry upload status ABRP Uploader Stop + Güncelleme kontrol et + Kontrol ediliyor… About Fatih\'sMG4 Hakkında Version %1$s Fork of malys / EVABRPUploader\nand Leon Kernan / ABRP_Uploader - github.com/fatihdonmezdev/EVABRP + github.com/fatihdonmezdev/MG4ABRP MG4 EH32 · SWI69 EVABRPUploader logo QR code for the project repository diff --git a/app/src/stable/java/com/evsuite/abrp/UpdateHook.java b/app/src/stable/java/com/evsuite/abrp/UpdateHook.java index 10b38ea..36f81a0 100644 --- a/app/src/stable/java/com/evsuite/abrp/UpdateHook.java +++ b/app/src/stable/java/com/evsuite/abrp/UpdateHook.java @@ -13,8 +13,21 @@ final class UpdateHook { private UpdateHook() { } + /** + * Mirrors the unstable seam's shape so shared UI code compiles in both flavours. + * Nothing implements it here. + */ + interface Listener { + void onResult(String message); + } + /** Does nothing. Stable users install updates themselves. */ static void checkInBackground(Context context) { } + /** Answers immediately that this channel does not self-update. */ + static void checkInBackground(Context context, Listener listener) { + if (listener != null) listener.onResult("Bu sürümde otomatik güncelleme yok"); + } + static boolean isSupported() { return false; } } diff --git a/app/src/unstable/java/com/evsuite/abrp/OtaUpdater.java b/app/src/unstable/java/com/evsuite/abrp/OtaUpdater.java index c906411..6d13261 100644 --- a/app/src/unstable/java/com/evsuite/abrp/OtaUpdater.java +++ b/app/src/unstable/java/com/evsuite/abrp/OtaUpdater.java @@ -46,7 +46,7 @@ final class OtaUpdater { * nothing until this repository publishes a release, which is the intended quiet. */ private static final String RELEASES_API = - "https://api.github.com/repos/fatihdonmezdev/EVABRP/releases"; + "https://api.github.com/repos/fatihdonmezdev/MG4ABRP/releases"; /** * Hosts an update may come from. The githubusercontent entries are the CDNs GitHub diff --git a/app/src/unstable/java/com/evsuite/abrp/UpdateHook.java b/app/src/unstable/java/com/evsuite/abrp/UpdateHook.java index 371c072..34e47ad 100644 --- a/app/src/unstable/java/com/evsuite/abrp/UpdateHook.java +++ b/app/src/unstable/java/com/evsuite/abrp/UpdateHook.java @@ -1,13 +1,89 @@ package com.evsuite.abrp; import android.content.Context; +import android.util.Log; + +import java.io.File; + /** - * OTA is suspended while the suite safety and legal audit is open. - * Keep this flavour seam inert until a reviewed change explicitly re-enables it. + * Drives {@link OtaUpdater} — UNSTABLE BUILDS ONLY. + * + * Upstream keeps this seam inert pending a safety and legal audit of the published suite. + * This fork publishes its own builds from its own repository, signed with the platform key + * the head unit already trusts, so that audit does not govern here and the seam is live. + * + * Installation goes through {@code pm install -r}, which only a platform-signed build may + * call. {@link OtaUpdater#install} checks the downloaded APK's certificate against the + * running app's first and refuses a mismatch, so an update signed with any other key is + * deleted rather than installed — the same check that makes an upstream APK unusable here. */ final class UpdateHook { + + private static final String TAG = "EVABRP.Update"; + private UpdateHook() { } - static boolean isSupported() { return false; } - static void checkInBackground(Context context) { } + static boolean isSupported() { return true; } + + /** Result of one check, for the UI to report. */ + interface Listener { + void onResult(String message); + } + + /** Fire-and-forget check on app start. Nothing is reported; failures stay in the log. */ + static void checkInBackground(Context context) { + checkInBackground(context, null); + } + + /** + * Checks, downloads and installs in one background pass. + * + * The whole sequence runs off the main thread because every step blocks: the release + * API call, the download, and {@code pm install}. [listener] is called back on the main + * thread, so a UI caller can write straight to a view. + */ + static void checkInBackground(Context context, Listener listener) { + Context app = context.getApplicationContext(); + android.os.Handler main = new android.os.Handler(android.os.Looper.getMainLooper()); + new Thread(() -> { + String message; + try { + message = runCheck(app); + } catch (Throwable t) { + Log.w(TAG, "Update check threw", t); + message = "Güncelleme kontrolü başarısız"; + } + if (listener != null) { + final String result = message; + main.post(() -> listener.onResult(result)); + } + }, "ota-check").start(); + } + + /** The blocking part. Returns a line describing what happened, for the UI. */ + private static String runCheck(Context app) { + String current; + try { + current = app.getPackageManager() + .getPackageInfo(app.getPackageName(), 0).versionName; + } catch (Exception e) { + return "Sürüm okunamadı"; + } + + OtaUpdater.Update update = OtaUpdater.check(current); + if (update == null) return "Güncel (" + current + ")"; + + Log.i(TAG, "Update available: " + update.versionName); + File apk = OtaUpdater.download(app, update); + if (apk == null) return "İndirme başarısız (" + update.versionName + ")"; + + // The cached APK is cleared whichever way install goes: a rejected archive must not + // linger, and an accepted one has already been copied out by the package manager. + boolean installed = OtaUpdater.install(app, apk); + if (!apk.delete()) Log.w(TAG, "Could not remove cached OTA APK"); + + return installed + ? "Güncellendi: " + update.versionName + " — yeniden başlatın" + : "Kurulum reddedildi (imza uyuşmuyor?)"; + } }