From 9ce24b22bf77f8734ce66d6b14ab67384b001207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20K=C4=99ska?= <372403+keskad@users.noreply.github.com> Date: Wed, 29 Jul 2026 09:18:39 +0200 Subject: [PATCH 01/17] =?UTF-8?q?feat(android):=20BigFred=20on=20phone=20?= =?UTF-8?q?=E2=80=94=20local=20FGS=20server=20with=20Valkey/supervisord?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- .gitignore | 4 + Makefile | 61 ++- app/build.gradle.kts | 122 ++++++ app/src/main/AndroidManifest.xml | 8 + .../android/data/ServerPreferences.kt | 16 + .../android/server/LocalServerPaths.kt | 28 ++ .../android/server/LocoServerService.kt | 359 ++++++++++++++++++ .../android/server/NativeBinaries.kt | 27 ++ .../android/server/ProcessOrphanReaper.kt | 70 ++++ .../android/server/SupervisorPathHelper.kt | 44 +++ .../android/ui/discovery/DiscoveryScreen.kt | 54 +++ .../ui/localserver/LocalServerStatusScreen.kt | 123 ++++++ .../android/ui/navigation/BigFredApp.kt | 105 ++++- .../android/ui/navigation/Routes.kt | 1 + app/src/main/res/values-de/strings.xml | 42 ++ app/src/main/res/values-pl/strings.xml | 42 ++ app/src/main/res/values/strings.xml | 42 ++ .../android/server/ProcessOrphanReaperTest.kt | 31 ++ gradle.properties | 10 + scripts/fetch-github-release-asset.sh | 57 +++ 20 files changed, 1226 insertions(+), 20 deletions(-) create mode 100644 app/src/main/java/com/dccbigfred/android/server/LocalServerPaths.kt create mode 100644 app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt create mode 100644 app/src/main/java/com/dccbigfred/android/server/NativeBinaries.kt create mode 100644 app/src/main/java/com/dccbigfred/android/server/ProcessOrphanReaper.kt create mode 100644 app/src/main/java/com/dccbigfred/android/server/SupervisorPathHelper.kt create mode 100644 app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerStatusScreen.kt create mode 100644 app/src/test/java/com/dccbigfred/android/server/ProcessOrphanReaperTest.kt create mode 100755 scripts/fetch-github-release-asset.sh diff --git a/.gitignore b/.gitignore index 8a87a8b..09c2766 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,7 @@ google-services.json app/src/main/assets/models/ tools/hydrus-import/out/ tools/hydrus-import/__pycache__/ + +# Native prebuilts (fetched from deps-android-* GitHub Releases) and staged jniLibs +native-prebuilt/ +app/src/main/jniLibs/ diff --git a/Makefile b/Makefile index 4dc8dae..0d5f46c 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,8 @@ # make debug — debug APK # make clean — remove build outputs # make import-models — fetch hydrus.pl catalog into assets/models/ +# make valkey-android — download libvalkey-server.so from deps-android-valkey latest release +# make supervisord-android — download supervisord libs from deps-android-supervisord latest release GRADLE ?= ./gradlew GRADLE_FLAGS ?= --quiet @@ -17,21 +19,34 @@ IMPORT_SCRIPT := tools/hydrus-import/import_models.py IMPORT_OUT := tools/hydrus-import/out ASSETS_MODELS := app/src/main/assets/models -.PHONY: help apk release test test-android debug clean import-models +NATIVE_PREBUILT := native-prebuilt/arm64-v8a +VALKEY_SO := $(NATIVE_PREBUILT)/libvalkey-server.so +SUPERVISORD_SO := $(NATIVE_PREBUILT)/libsupervisord.so +SUPERVISORCTL_SO := $(NATIVE_PREBUILT)/libsupervisorctl.so + +VALKEY_REPO ?= dcc-bigfred/deps-android-valkey +SUPERVISORD_REPO ?= dcc-bigfred/deps-android-supervisord + +.PHONY: help apk release test test-android debug clean import-models \ + valkey-android supervisord-android native-prebuilt help: @echo "Targets:" - @echo " make apk Build signed release APK → $(APK_RELEASE)" - @echo " make release Alias for apk" - @echo " make test Run JVM unit tests" - @echo " make test-android Run instrumented tests (device/emulator required)" - @echo " make debug Build debug APK → $(APK_DEBUG)" - @echo " make import-models Import hydrus models DB + thumbs → $(ASSETS_MODELS)" - @echo " make clean Clean Gradle build outputs" + @echo " make apk Build signed release APK → $(APK_RELEASE)" + @echo " make release Alias for apk" + @echo " make test Run JVM unit tests" + @echo " make test-android Run instrumented tests (device/emulator required)" + @echo " make debug Build debug APK → $(APK_DEBUG)" + @echo " make import-models Import hydrus models DB + thumbs → $(ASSETS_MODELS)" + @echo " make valkey-android Fetch $(VALKEY_SO) from $(VALKEY_REPO) latest release (skip if exists; FORCE=1)" + @echo " make supervisord-android Fetch supervisord libs from $(SUPERVISORD_REPO) latest release (skip if exists; FORCE=1)" + @echo " make clean Clean Gradle build outputs" @echo "" @echo "Release signing (optional; falls back to debug keystore):" @echo " BIGFRED_STORE_FILE / BIGFRED_STORE_PASSWORD" @echo " BIGFRED_KEY_ALIAS / BIGFRED_KEY_PASSWORD" + @echo "" + @echo "Private GitHub deps (optional): GITHUB_TOKEN / GH_TOKEN / BIGFRED_NATIVE_TOKEN" import-models: $(PYTHON) "$(IMPORT_SCRIPT)" --out "$(IMPORT_OUT)" @@ -43,12 +58,38 @@ import-models: @ls -lh "$(ASSETS_MODELS)/models.db" @echo "Images: $$(find "$(ASSETS_MODELS)/images" -type f | wc -l)" -apk release: +# --- Native prebuilts (download from deps-* GitHub Releases) ----------------- +# Thin Make rules: skip when the output exists. FORCE=1 removes first. + +ifdef FORCE +.PHONY: force-clean-native +force-clean-native: + rm -f "$(VALKEY_SO)" "$(SUPERVISORD_SO)" "$(SUPERVISORCTL_SO)" +valkey-android: force-clean-native +supervisord-android: force-clean-native +endif + +valkey-android: $(VALKEY_SO) + +$(VALKEY_SO): + ./scripts/fetch-github-release-asset.sh "$(VALKEY_REPO)" libvalkey-server.so "$@" + +supervisord-android: $(SUPERVISORD_SO) $(SUPERVISORCTL_SO) + +$(SUPERVISORD_SO): + ./scripts/fetch-github-release-asset.sh "$(SUPERVISORD_REPO)" libsupervisord.so "$@" + +$(SUPERVISORCTL_SO): + ./scripts/fetch-github-release-asset.sh "$(SUPERVISORD_REPO)" libsupervisorctl.so "$@" + +native-prebuilt: valkey-android supervisord-android + +apk release: valkey-android supervisord-android $(GRADLE) $(GRADLE_FLAGS) :app:assembleRelease @echo "APK: $(APK_RELEASE)" @ls -lh "$(APK_RELEASE)" -debug: +debug: valkey-android supervisord-android $(GRADLE) $(GRADLE_FLAGS) :app:assembleDebug @echo "APK: $(APK_DEBUG)" @ls -lh "$(APK_DEBUG)" diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ea86008..0abef76 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -5,6 +5,10 @@ plugins { id("com.google.devtools.ksp") } +import java.io.File +import java.net.HttpURLConnection +import java.net.URI + fun gitCommand(vararg args: String): String { return try { val process = ProcessBuilder("git", *args) @@ -44,6 +48,10 @@ android { buildConfigField("String", "GIT_COMMIT", "\"$gitCommitShort\"") buildConfigField("String", "GIT_COMMIT_FULL", "\"$gitCommitFull\"") buildConfigField("boolean", "GIT_DIRTY", "$gitDirty") + + ndk { + abiFilters += "arm64-v8a" + } } signingConfigs { @@ -90,6 +98,9 @@ android { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } + jniLibs { + useLegacyPackaging = true + } } androidResources { @@ -97,6 +108,117 @@ android { } } +// Stage native executables into jniLibs as lib*.so (executable from nativeLibraryDir). +val fetchNativeBinaries by tasks.registering { + val jniOut = layout.projectDirectory.dir("src/main/jniLibs/arm64-v8a") + val prebuilt = rootProject.layout.projectDirectory.dir("native-prebuilt/arm64-v8a") + val localLoco = rootProject.layout.projectDirectory.dir("../bigfred/bin") + .file("loco-server-android-arm64") + val fetchScript = rootProject.layout.projectDirectory.file("scripts/fetch-github-release-asset.sh") + outputs.dir(jniOut) + doLast { + val outDir = jniOut.asFile + outDir.mkdirs() + + fun githubToken(): String? = + System.getenv("BIGFRED_NATIVE_TOKEN") + ?: System.getenv("GH_TOKEN") + ?: System.getenv("GITHUB_TOKEN") + + fun copyIfExists(src: File, destName: String): Boolean { + if (!src.isFile) return false + src.copyTo(File(outDir, destName), overwrite = true) + println("Staged $destName from ${src.absolutePath}") + return true + } + + fun fetchLatestReleaseAsset(repo: String, assetName: String, dest: File) { + val script = fetchScript.asFile + require(script.isFile) { "Missing fetch script: ${script.absolutePath}" } + val pb = ProcessBuilder(script.absolutePath, repo, assetName, dest.absolutePath) + pb.directory(rootProject.projectDir) + pb.redirectErrorStream(true) + val env = pb.environment() + githubToken()?.let { env["GITHUB_TOKEN"] = it } + val proc = pb.start() + val output = proc.inputStream.bufferedReader().readText() + val code = proc.waitFor() + print(output) + if (code != 0) { + throw GradleException("Failed to fetch $assetName from $repo (exit $code)") + } + } + + fun stageOrFetch(prebuiltName: String, repoProp: String, defaultRepo: String) { + val dest = File(outDir, prebuiltName) + if (copyIfExists(prebuilt.file(prebuiltName).asFile, prebuiltName)) return + val repo = (project.findProperty(repoProp) as String?) + ?.ifBlank { null } + ?: defaultRepo + fetchLatestReleaseAsset(repo, prebuiltName, dest) + // Keep native-prebuilt in sync for make skip-if-exists. + val cache = prebuilt.file(prebuiltName).asFile + cache.parentFile.mkdirs() + dest.copyTo(cache, overwrite = true) + } + + // Valkey / supervisord: prefer make-fetched native-prebuilt, else latest GitHub release. + stageOrFetch( + "libvalkey-server.so", + "depsAndroidValkeyRepo", + "dcc-bigfred/deps-android-valkey", + ) + stageOrFetch( + "libsupervisord.so", + "depsAndroidSupervisordRepo", + "dcc-bigfred/deps-android-supervisord", + ) + stageOrFetch( + "libsupervisorctl.so", + "depsAndroidSupervisordRepo", + "dcc-bigfred/deps-android-supervisord", + ) + + val locoDest = File(outDir, "libloco-server.so") + if (localLoco.asFile.isFile) { + localLoco.asFile.copyTo(locoDest, overwrite = true) + println("Staged libloco-server.so from local ${localLoco.asFile}") + } else { + val repo = (project.findProperty("bigfredNativeRepo") as String?) + ?.ifBlank { null } + ?: System.getenv("BIGFRED_NATIVE_REPO") + val tag = (project.findProperty("bigfredNativeTag") as String?) + ?.ifBlank { null } + ?: System.getenv("BIGFRED_NATIVE_TAG") + if (repo.isNullOrBlank() || tag.isNullOrBlank()) { + logger.warn( + "libloco-server.so missing: build ../bigfred with 'make android' " + + "or set bigfredNativeRepo + bigfredNativeTag", + ) + } else { + val token = githubToken() + val url = + "https://github.com/$repo/releases/download/$tag/loco-server-android-arm64" + val tmp = File(outDir, ".loco-server.download") + val conn = URI(url).toURL().openConnection() as HttpURLConnection + if (!token.isNullOrBlank()) { + conn.setRequestProperty("Authorization", "Bearer $token") + conn.setRequestProperty("Accept", "application/octet-stream") + } + conn.inputStream.use { input -> + tmp.outputStream().use { output -> input.copyTo(output) } + } + tmp.renameTo(locoDest) + println("Downloaded libloco-server.so from $url") + } + } + } +} + +tasks.named("preBuild").configure { + dependsOn(fetchNativeBinaries) +} + dependencies { val composeBom = platform("androidx.compose:compose-bom:2024.10.01") implementation(composeBom) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 306cfcc..af496c0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,9 @@ + + + + + + prefs[localJwtSecretKey] = generated + } + return generated + } + suspend fun setThemeMode(mode: ThemeMode) { themeSyncPrefs.edit().putString(THEME_SYNC_KEY, mode.storageValue).apply() context.dataStore.edit { prefs -> diff --git a/app/src/main/java/com/dccbigfred/android/server/LocalServerPaths.kt b/app/src/main/java/com/dccbigfred/android/server/LocalServerPaths.kt new file mode 100644 index 0000000..561479c --- /dev/null +++ b/app/src/main/java/com/dccbigfred/android/server/LocalServerPaths.kt @@ -0,0 +1,28 @@ +package com.dccbigfred.android.server + +import android.content.Context +import java.io.File + +/** Writable BigFred data tree under the app files directory. */ +data class LocalServerPaths( + val dataDir: File, +) { + val etcDir: File get() = File(dataDir, "etc") + val runDir: File get() = File(dataDir, "run") + val logsDir: File get() = File(dataDir, "logs") + val redisDir: File get() = File(dataDir, "redis") + + val locoServerPid: File get() = File(runDir, "loco-server.pid") + val valkeyPid: File get() = File(runDir, "valkey.pid") + val supervisordPid: File get() = File(runDir, "supervisord.pid") + val dbFile: File get() = File(dataDir, "bigfred.db") + + fun ensureDirs() { + listOf(dataDir, etcDir, runDir, logsDir, redisDir).forEach { it.mkdirs() } + } + + companion object { + fun from(context: Context): LocalServerPaths = + LocalServerPaths(File(context.filesDir, "bigfred-data")) + } +} diff --git a/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt b/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt new file mode 100644 index 0000000..d1b3125 --- /dev/null +++ b/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt @@ -0,0 +1,359 @@ +package com.dccbigfred.android.server + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.Service +import android.content.Context +import android.content.Intent +import android.content.pm.ServiceInfo +import android.os.Build +import android.os.IBinder +import android.os.PowerManager +import android.util.Log +import androidx.core.app.NotificationCompat +import androidx.core.app.ServiceCompat +import com.dccbigfred.android.BigFredApplication +import com.dccbigfred.android.MainActivity +import com.dccbigfred.android.R +import com.dccbigfred.android.network.ServerProbe +import java.io.File +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.concurrent.thread +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.runBlocking + +/** + * Foreground service hosting Valkey + loco-server for "BigFred on phone". + * Stage 1 uses `--no-supervisor`; stage 2 prepares PATH for supervisord binaries. + */ +class LocoServerService : Service() { + + private var wakeLock: PowerManager.WakeLock? = null + private var valkeyProcess: Process? = null + private var locoProcess: Process? = null + private var watchdogThread: Thread? = null + private val running = AtomicBoolean(false) + + override fun onBind(intent: Intent?): IBinder? = null + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + when (intent?.action) { + ACTION_STOP -> { + stopLocalServer() + stopSelf() + return START_NOT_STICKY + } + ACTION_START, null -> { + if (!running.get()) { + startForegroundNotification() + acquireWakeLock() + thread(name = "loco-server-boot", isDaemon = true) { + try { + boot() + } catch (e: Exception) { + Log.e(TAG, "local server start failed", e) + _state.value = LocalServerState.Failed(e.message ?: e.toString()) + stopLocalServer() + stopSelf() + } + } + } + } + } + return START_STICKY + } + + override fun onDestroy() { + stopLocalServer() + releaseWakeLock() + super.onDestroy() + } + + private fun boot() { + _state.value = LocalServerState.Starting + val paths = LocalServerPaths.from(this) + paths.ensureDirs() + + ProcessOrphanReaper.reap(paths.locoServerPid, NativeBinaries.LOCO_SERVER) + ProcessOrphanReaper.reap(paths.valkeyPid, NativeBinaries.VALKEY) + ProcessOrphanReaper.reap(paths.supervisordPid, NativeBinaries.SUPERVISORD) + + if (ProcessOrphanReaper.isPortOpen("127.0.0.1", HTTP_PORT) && + ProcessOrphanReaper.readPid(paths.locoServerPid) == null + ) { + throw IllegalStateException( + "Port $HTTP_PORT is already in use without a known loco-server pidfile", + ) + } + + val locoBin = NativeBinaries.require(this, NativeBinaries.LOCO_SERVER) + val valkeyBin = NativeBinaries.require(this, NativeBinaries.VALKEY) + val supervised = SupervisorPathHelper.installSupervisorBinaries(this) != null && + NativeBinaries.file(this, NativeBinaries.SUPERVISORD).isFile && + NativeBinaries.file(this, NativeBinaries.SUPERVISORCTL).isFile + + val prefs = (application as BigFredApplication).serverPreferences + val jwt = runBlocking { prefs.getOrCreateLocalJwtSecret() } + + val env = HashMap(System.getenv()) + env["BIGFRED_DATA_DIR"] = paths.dataDir.absolutePath + env["BIGFRED_JWT_SECRET"] = jwt + env["PATH"] = SupervisorPathHelper.prependPath( + env["PATH"], + supervisorBinDir().absolutePath, + ) + + if (!supervised) { + // Stage 1: FGS owns Valkey; loco-server runs without supervisord. + bootUnmanagedValkey(paths, valkeyBin) + } + + val locoLog = File(paths.logsDir, "loco-server.log") + val locoArgs = mutableListOf( + locoBin.absolutePath, + "--http", "0.0.0.0:$HTTP_PORT", + "--db", paths.dbFile.absolutePath, + "--redis-bin", valkeyBin.absolutePath, + "--redis-addr", "127.0.0.1:$REDIS_PORT", + "--mdns=false", + ) + if (supervised) { + // Stage 2: loco-server owns supervisord → Valkey + dcc-bus. + Log.i(TAG, "starting supervised local mode (supervisord on PATH)") + } else { + locoArgs += listOf("--redis-external", "--no-supervisor") + Log.i(TAG, "starting unmanaged local mode (--no-supervisor)") + } + + locoProcess = ProcessBuilder(locoArgs) + .directory(paths.dataDir) + .redirectErrorStream(true) + .redirectOutput(ProcessBuilder.Redirect.appendTo(locoLog)) + .also { it.environment().clear(); it.environment().putAll(env) } + .start() + ProcessOrphanReaper.writePid(paths.locoServerPid, processPid(locoProcess!!)) + + waitForHttpReady(45_000) + running.set(true) + _state.value = LocalServerState.Running(LOCAL_BASE_URL) + startWatchdog(supervised) + } + + private fun bootUnmanagedValkey(paths: LocalServerPaths, valkeyBin: File) { + val valkeyLog = File(paths.logsDir, "valkey.log") + valkeyProcess = ProcessBuilder( + valkeyBin.absolutePath, + "--bind", "127.0.0.1", + "--port", REDIS_PORT.toString(), + "--dir", paths.redisDir.absolutePath, + "--save", "", + "--appendonly", "no", + "--protected-mode", "no", + "--daemonize", "no", + ).redirectErrorStream(true) + .redirectOutput(ProcessBuilder.Redirect.appendTo(valkeyLog)) + .start() + ProcessOrphanReaper.writePid(paths.valkeyPid, processPid(valkeyProcess!!)) + waitForPort("127.0.0.1", REDIS_PORT, 15_000) + } + + private fun waitForPort(host: String, port: Int, timeoutMs: Long) { + val deadline = System.currentTimeMillis() + timeoutMs + while (System.currentTimeMillis() < deadline) { + if (ProcessOrphanReaper.isPortOpen(host, port)) return + Thread.sleep(200) + } + throw IllegalStateException("Timeout waiting for $host:$port") + } + + private fun waitForHttpReady(timeoutMs: Long) { + val probe = ServerProbe() + val deadline = System.currentTimeMillis() + timeoutMs + while (System.currentTimeMillis() < deadline) { + val ok = runBlocking { probe.isReachable(LOCAL_BASE_URL) } + if (ok) return + Thread.sleep(300) + } + throw IllegalStateException("Timeout waiting for $LOCAL_BASE_URL") + } + + private fun startWatchdog(supervised: Boolean) { + watchdogThread?.interrupt() + watchdogThread = thread(name = "loco-server-watchdog", isDaemon = true) { + while (running.get() && !Thread.currentThread().isInterrupted) { + try { + Thread.sleep(3_000) + val locoAlive = locoProcess?.isAlive == true + val valkeyOk = supervised || valkeyProcess?.isAlive == true + if (!locoAlive || !valkeyOk) { + Log.w(TAG, "child died loco=$locoAlive valkeyOk=$valkeyOk — restarting") + stopChildren() + running.set(false) + boot() + return@thread + } + } catch (_: InterruptedException) { + return@thread + } catch (e: Exception) { + Log.e(TAG, "watchdog restart failed", e) + _state.value = LocalServerState.Failed(e.message ?: e.toString()) + running.set(false) + stopSelf() + return@thread + } + } + } + } + + private fun stopLocalServer() { + running.set(false) + watchdogThread?.interrupt() + watchdogThread = null + stopChildren() + val paths = LocalServerPaths.from(this) + paths.locoServerPid.delete() + paths.valkeyPid.delete() + paths.supervisordPid.delete() + _state.value = LocalServerState.Stopped + } + + private fun stopChildren() { + listOf(locoProcess, valkeyProcess).forEach { proc -> + proc ?: return@forEach + try { + proc.destroy() + if (!proc.waitFor(3, java.util.concurrent.TimeUnit.SECONDS)) { + proc.destroyForcibly() + } + } catch (_: Exception) { + } + } + locoProcess = null + valkeyProcess = null + } + + /** Copy supervisord/ctl into codeCacheDir/bin so LookPath finds bare names. */ + // Kept for tests / callers; delegates to SupervisorPathHelper. + fun installSupervisorPathHelpers() { + SupervisorPathHelper.installSupervisorBinaries(this) + } + + private fun supervisorBinDir(): File = + File(codeCacheDir, "bin") + + /** java.lang.Process.pid() is only in the public Android SDK from API 31+. */ + private fun processPid(process: Process): Long { + return try { + val method = Process::class.java.getMethod("pid") + (method.invoke(process) as Long) + } catch (_: Exception) { + Regex("""pid[= ](\d+)""") + .find(process.toString()) + ?.groupValues + ?.getOrNull(1) + ?.toLongOrNull() + ?: -1L + } + } + + private fun startForegroundNotification() { + ensureChannel() + val openIntent = PendingIntent.getActivity( + this, + 0, + Intent(this, MainActivity::class.java), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, + ) + val stopIntent = PendingIntent.getService( + this, + 1, + Intent(this, LocoServerService::class.java).setAction(ACTION_STOP), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, + ) + val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle(getString(R.string.local_server_notification_title)) + .setContentText(getString(R.string.local_server_notification_text)) + .setSmallIcon(R.mipmap.ic_launcher) + .setContentIntent(openIntent) + .addAction(0, getString(R.string.local_server_stop), stopIntent) + .setOngoing(true) + .setOnlyAlertOnce(true) + .build() + + if (Build.VERSION.SDK_INT >= 34) { + ServiceCompat.startForeground( + this, + NOTIFICATION_ID, + notification, + ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE, + ) + } else { + startForeground(NOTIFICATION_ID, notification) + } + } + + private fun ensureChannel() { + val mgr = getSystemService(NotificationManager::class.java) ?: return + val channel = NotificationChannel( + CHANNEL_ID, + getString(R.string.local_server_notification_channel), + NotificationManager.IMPORTANCE_LOW, + ) + mgr.createNotificationChannel(channel) + } + + private fun acquireWakeLock() { + if (wakeLock?.isHeld == true) return + val pm = getSystemService(PowerManager::class.java) ?: return + wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "bigfred:local-server").also { + it.setReferenceCounted(false) + it.acquire() + } + } + + private fun releaseWakeLock() { + try { + wakeLock?.takeIf { it.isHeld }?.release() + } catch (_: Exception) { + } + wakeLock = null + } + + companion object { + private const val TAG = "LocoServerService" + private const val CHANNEL_ID = "bigfred_local_server" + private const val NOTIFICATION_ID = 42 + const val HTTP_PORT = 8080 + const val REDIS_PORT = 6379 + const val LOCAL_BASE_URL = "http://127.0.0.1:$HTTP_PORT" + const val ACTION_START = "com.dccbigfred.android.server.START" + const val ACTION_STOP = "com.dccbigfred.android.server.STOP" + + private val _state = MutableStateFlow(LocalServerState.Stopped) + val state: StateFlow = _state.asStateFlow() + + fun start(context: Context) { + val intent = Intent(context, LocoServerService::class.java).setAction(ACTION_START) + context.startForegroundService(intent) + } + + fun stop(context: Context) { + val intent = Intent(context, LocoServerService::class.java).setAction(ACTION_STOP) + context.startService(intent) + } + + fun isLocalUrl(url: String?): Boolean = + url != null && (url.contains("127.0.0.1") || url.contains("localhost")) + } +} + +sealed class LocalServerState { + data object Stopped : LocalServerState() + data object Starting : LocalServerState() + data class Running(val baseUrl: String) : LocalServerState() + data class Failed(val message: String) : LocalServerState() +} diff --git a/app/src/main/java/com/dccbigfred/android/server/NativeBinaries.kt b/app/src/main/java/com/dccbigfred/android/server/NativeBinaries.kt new file mode 100644 index 0000000..f04c25f --- /dev/null +++ b/app/src/main/java/com/dccbigfred/android/server/NativeBinaries.kt @@ -0,0 +1,27 @@ +package com.dccbigfred.android.server + +import android.content.Context +import android.content.pm.ApplicationInfo +import java.io.File + +/** Resolves extracted jniLibs executables under [ApplicationInfo.nativeLibraryDir]. */ +object NativeBinaries { + const val LOCO_SERVER = "libloco-server.so" + const val VALKEY = "libvalkey-server.so" + const val SUPERVISORD = "libsupervisord.so" + const val SUPERVISORCTL = "libsupervisorctl.so" + + fun dir(context: Context): File = + File(context.applicationInfo.nativeLibraryDir) + + fun file(context: Context, name: String): File = + File(dir(context), name) + + fun require(context: Context, name: String): File { + val f = file(context, name) + if (!f.isFile) { + throw IllegalStateException("Native binary missing: ${f.absolutePath}") + } + return f + } +} diff --git a/app/src/main/java/com/dccbigfred/android/server/ProcessOrphanReaper.kt b/app/src/main/java/com/dccbigfred/android/server/ProcessOrphanReaper.kt new file mode 100644 index 0000000..394ffa7 --- /dev/null +++ b/app/src/main/java/com/dccbigfred/android/server/ProcessOrphanReaper.kt @@ -0,0 +1,70 @@ +package com.dccbigfred.android.server + +import android.system.Os +import android.system.OsConstants +import java.io.File +import java.net.InetSocketAddress +import java.net.Socket + +/** + * Cleans up orphaned loco-server / valkey processes left after force-stop. + * Verifies /proc//cmdline before signaling to avoid PID reuse kills. + */ +object ProcessOrphanReaper { + + fun reap(pidFile: File, cmdlineNeedle: String) { + val pid = readPid(pidFile) ?: return + if (!cmdlineMatches(pid, cmdlineNeedle)) { + pidFile.delete() + return + } + signal(pid, OsConstants.SIGTERM) + val deadline = System.currentTimeMillis() + 3_000 + while (System.currentTimeMillis() < deadline) { + if (!isAlive(pid)) break + Thread.sleep(100) + } + if (isAlive(pid)) { + signal(pid, OsConstants.SIGKILL) + } + pidFile.delete() + } + + fun isPortOpen(host: String, port: Int, timeoutMs: Int = 300): Boolean = + try { + Socket().use { s -> + s.connect(InetSocketAddress(host, port), timeoutMs) + true + } + } catch (_: Exception) { + false + } + + fun readPid(pidFile: File): Int? { + if (!pidFile.isFile) return null + return pidFile.readText().trim().toIntOrNull()?.takeIf { it > 0 } + } + + fun writePid(pidFile: File, pid: Long) { + pidFile.parentFile?.mkdirs() + pidFile.writeText(pid.toString()) + } + + fun cmdlineMatches(pid: Int, needle: String): Boolean { + val cmdline = File("/proc/$pid/cmdline") + if (!cmdline.isFile) return false + val text = cmdline.readBytes().toString(Charsets.UTF_8).replace('\u0000', ' ') + return text.contains(needle) + } + + fun isAlive(pid: Int): Boolean = + File("/proc/$pid").isDirectory + + private fun signal(pid: Int, sig: Int) { + try { + Os.kill(pid, sig) + } catch (_: Exception) { + // already gone + } + } +} diff --git a/app/src/main/java/com/dccbigfred/android/server/SupervisorPathHelper.kt b/app/src/main/java/com/dccbigfred/android/server/SupervisorPathHelper.kt new file mode 100644 index 0000000..dace936 --- /dev/null +++ b/app/src/main/java/com/dccbigfred/android/server/SupervisorPathHelper.kt @@ -0,0 +1,44 @@ +package com.dccbigfred.android.server + +import android.content.Context +import java.io.File + +/** + * Stage 2 scaffolding: expose supervisord/supervisorctl on PATH under [Context.getCodeCacheDir]. + * Stage 1 still runs loco-server with `--no-supervisor`. + */ +object SupervisorPathHelper { + private const val BIN_SUBDIR = "bin" + + fun installSupervisorBinaries(context: Context): String? { + val binDir = File(context.codeCacheDir, BIN_SUBDIR) + binDir.mkdirs() + var installed = false + listOf( + NativeBinaries.SUPERVISORD to "supervisord", + NativeBinaries.SUPERVISORCTL to "supervisorctl", + ).forEach { (libName, destName) -> + val src = NativeBinaries.file(context, libName) + if (!src.isFile) return@forEach + copyExecutable(src, File(binDir, destName)) + installed = true + } + return if (installed) binDir.absolutePath else null + } + + fun prependPath(existingPath: String?, binDir: String): String { + val trimmed = binDir.trimEnd('/') + return when { + existingPath.isNullOrBlank() -> trimmed + existingPath.split(':').any { it == trimmed } -> existingPath + else -> "$trimmed:$existingPath" + } + } + + private fun copyExecutable(source: File, dest: File) { + source.inputStream().use { input -> + dest.outputStream().use { output -> input.copyTo(output) } + } + dest.setExecutable(true, false) + } +} diff --git a/app/src/main/java/com/dccbigfred/android/ui/discovery/DiscoveryScreen.kt b/app/src/main/java/com/dccbigfred/android/ui/discovery/DiscoveryScreen.kt index 8fb4dc2..a5d56a2 100644 --- a/app/src/main/java/com/dccbigfred/android/ui/discovery/DiscoveryScreen.kt +++ b/app/src/main/java/com/dccbigfred/android/ui/discovery/DiscoveryScreen.kt @@ -34,7 +34,10 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar +import androidx.compose.material.icons.filled.PhoneAndroid import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue @@ -63,6 +66,9 @@ import kotlinx.coroutines.launch @Composable fun DiscoveryScreen( onServerSelected: (String) -> Unit, + onStartLocalServer: () -> Unit = {}, + localServerRunning: Boolean = false, + onOpenLocalStatus: () -> Unit = {}, ) { val context = LocalContext.current val discovery = remember { ServerDiscovery(context) } @@ -75,6 +81,7 @@ fun DiscoveryScreen( var manualError by remember { mutableStateOf(null) } var manualBusy by remember { mutableStateOf(false) } var manualExpanded by remember { mutableStateOf(false) } + var showLocalWarning by remember { mutableStateOf(false) } val errorHostRequired = stringResource(R.string.discovery_error_host_required) val errorUnreachable = stringResource(R.string.discovery_error_unreachable) @@ -152,6 +159,30 @@ fun DiscoveryScreen( ) Spacer(modifier = Modifier.height(12.dp)) + if (localServerRunning) { + ListItem( + headlineContent = { + Text(stringResource(R.string.discovery_on_phone_running)) + }, + leadingContent = { + Icon(Icons.Default.PhoneAndroid, contentDescription = null) + }, + modifier = Modifier.clickable(onClick = onOpenLocalStatus), + ) + HorizontalDivider() + Spacer(modifier = Modifier.height(8.dp)) + } else { + Button( + onClick = { showLocalWarning = true }, + modifier = Modifier.fillMaxWidth(), + ) { + Icon(Icons.Default.PhoneAndroid, contentDescription = null) + Spacer(modifier = Modifier.width(8.dp)) + Text(stringResource(R.string.discovery_on_phone_title)) + } + Spacer(modifier = Modifier.height(12.dp)) + } + if (scanning) { Row(verticalAlignment = Alignment.CenterVertically) { CircularProgressIndicator(modifier = Modifier.size(20.dp), strokeWidth = 2.dp) @@ -256,6 +287,29 @@ fun DiscoveryScreen( } } } + + if (showLocalWarning) { + AlertDialog( + onDismissRequest = { showLocalWarning = false }, + title = { Text(stringResource(R.string.discovery_on_phone_warning_title)) }, + text = { Text(stringResource(R.string.discovery_on_phone_warning_body)) }, + confirmButton = { + TextButton( + onClick = { + showLocalWarning = false + onStartLocalServer() + }, + ) { + Text(stringResource(R.string.discovery_on_phone_confirm)) + } + }, + dismissButton = { + TextButton(onClick = { showLocalWarning = false }) { + Text(stringResource(R.string.discovery_on_phone_cancel)) + } + }, + ) + } } @Composable diff --git a/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerStatusScreen.kt b/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerStatusScreen.kt new file mode 100644 index 0000000..25a557b --- /dev/null +++ b/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerStatusScreen.kt @@ -0,0 +1,123 @@ +package com.dccbigfred.android.ui.localserver + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.dccbigfred.android.R +import com.dccbigfred.android.server.LocalServerPaths +import com.dccbigfred.android.server.LocalServerState +import com.dccbigfred.android.server.LocoServerService +import com.dccbigfred.android.ui.components.topAppBarEdgePadding +import java.io.File + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun LocalServerStatusScreen( + onOpenApp: () -> Unit, + onStopped: () -> Unit, +) { + val context = LocalContext.current + val state by LocoServerService.state.collectAsStateWithLifecycle() + val logs = remember(state) { + val dir = LocalServerPaths.from(context).logsDir + listOf("loco-server.log", "valkey.log") + .map { File(dir, it) } + .filter { it.isFile } + .joinToString("\n\n") { f -> + "--- ${f.name} ---\n" + f.readLines().takeLast(40).joinToString("\n") + } + .ifBlank { "" } + } + + Scaffold( + topBar = { + TopAppBar( + modifier = Modifier.topAppBarEdgePadding(), + title = { Text(stringResource(R.string.local_server_status_title)) }, + ) + }, + ) { padding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .padding(16.dp) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + Text( + text = when (val s = state) { + LocalServerState.Stopped -> stringResource(R.string.local_server_status_stopped) + LocalServerState.Starting -> stringResource(R.string.local_server_status_starting) + is LocalServerState.Running -> + stringResource(R.string.local_server_status_running, s.baseUrl) + is LocalServerState.Failed -> + stringResource(R.string.local_server_status_failed, s.message) + }, + style = MaterialTheme.typography.bodyLarge, + ) + + if (state is LocalServerState.Running) { + Button( + onClick = onOpenApp, + modifier = Modifier.fillMaxWidth(), + ) { + Text(stringResource(R.string.local_server_open_app)) + } + } + + OutlinedButton( + onClick = { + LocoServerService.stop(context) + LocoServerService.start(context) + }, + modifier = Modifier.fillMaxWidth(), + enabled = state !is LocalServerState.Starting, + ) { + Text(stringResource(R.string.local_server_restart)) + } + + Button( + onClick = { + LocoServerService.stop(context) + onStopped() + }, + modifier = Modifier.fillMaxWidth(), + ) { + Text(stringResource(R.string.local_server_stop)) + } + + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = stringResource(R.string.local_server_logs), + style = MaterialTheme.typography.titleMedium, + ) + Text( + text = logs.ifBlank { "—" }, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } +} diff --git a/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt b/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt index 1bada0c..30983cf 100644 --- a/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt +++ b/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt @@ -29,6 +29,7 @@ import androidx.compose.material.icons.filled.NetworkCheck import androidx.compose.material.icons.filled.PhoneAndroid import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Settings +import androidx.compose.material3.AlertDialog import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.DrawerValue import androidx.compose.material3.HorizontalDivider @@ -39,6 +40,7 @@ import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.NavigationDrawerItem import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -71,9 +73,12 @@ import com.dccbigfred.android.data.ServerPreferences import com.dccbigfred.android.locale.LocalePrefs import com.dccbigfred.android.network.CompanionServiceProbe import com.dccbigfred.android.network.CompanionServices +import com.dccbigfred.android.server.LocalServerState +import com.dccbigfred.android.server.LocoServerService import com.dccbigfred.android.ui.about.AboutScreen import com.dccbigfred.android.ui.connection.ConnectionStatusScreen import com.dccbigfred.android.ui.discovery.DiscoveryScreen +import com.dccbigfred.android.ui.localserver.LocalServerStatusScreen import com.dccbigfred.android.ui.models.ModelsCatalogScreen import com.dccbigfred.android.ui.myvehicles.MyVehiclesScreen import com.dccbigfred.android.ui.myvehicles.MyVehiclesViewModel @@ -217,11 +222,52 @@ fun BigFredApp() { } } + val localServerState by LocoServerService.state.collectAsStateWithLifecycle() + var localStartError by remember { mutableStateOf(null) } + + fun startLocalServer() { + scope.launch { + localStartError = null + LocoServerService.start(context) + // Wait until running or failed (bounded). + val deadline = System.currentTimeMillis() + 60_000 + while (System.currentTimeMillis() < deadline) { + when (val s = LocoServerService.state.value) { + is LocalServerState.Running -> { + goToWebView(s.baseUrl) + return@launch + } + is LocalServerState.Failed -> { + localStartError = s.message + return@launch + } + else -> kotlinx.coroutines.delay(300) + } + } + localStartError = "timeout" + } + } + + fun stopLocalServerAndDiscover() { + scope.launch { + LocoServerService.stop(context) + prefs.clearServerBaseUrl() + activeUrl = null + navController.navigate(Routes.DISCOVERY) { + popUpTo(navController.graph.id) { inclusive = true } + launchSingleTop = true + } + drawerState.close() + } + } + fun logoutFromServer() { scope.launch { drawerState.close() val url = selectedServerUrl - if (url != null) { + if (LocoServerService.isLocalUrl(url)) { + LocoServerService.stop(context) + } else if (url != null) { app.bigFredApiClient.logout(url) } prefs.clearServerBaseUrl() @@ -259,6 +305,23 @@ fun BigFredApp() { icon = { Icon(Icons.Default.Home, contentDescription = null) }, onClick = { openBigFredApp() }, ) + if (localServerState is LocalServerState.Running || + LocoServerService.isLocalUrl(selectedServerUrl) + ) { + NavigationDrawerItem( + label = { Text(stringResource(R.string.menu_local_server)) }, + selected = currentRoute == Routes.LOCAL_SERVER, + icon = { Icon(Icons.Default.PhoneAndroid, contentDescription = null) }, + onClick = { + scope.launch { + drawerState.close() + navController.navigate(Routes.LOCAL_SERVER) { + launchSingleTop = true + } + } + }, + ) + } NavigationDrawerItem( label = { Text(stringResource(R.string.menu_settings)) }, selected = currentRoute == Routes.SETTINGS, @@ -429,7 +492,22 @@ fun BigFredApp() { } } composable(Routes.DISCOVERY) { - DiscoveryScreen(onServerSelected = { url -> goToWebView(url) }) + DiscoveryScreen( + onServerSelected = { url -> goToWebView(url) }, + onStartLocalServer = { startLocalServer() }, + localServerRunning = localServerState is LocalServerState.Running, + onOpenLocalStatus = { + navController.navigate(Routes.LOCAL_SERVER) { + launchSingleTop = true + } + }, + ) + } + composable(Routes.LOCAL_SERVER) { + LocalServerStatusScreen( + onOpenApp = { openBigFredApp() }, + onStopped = { stopLocalServerAndDiscover() }, + ) } composable(Routes.WEBVIEW) { if (webSessionUrl == null) { @@ -485,14 +563,6 @@ fun BigFredApp() { composable(Routes.MODELS) { ModelsCatalogScreen( onBack = { navController.popBackStack() }, - onAddToMyVehicles = { row -> - scope.launch { - val repo = app.localVehicleRepository - repo.upsert( - MyVehiclesViewModel.fromModelRow(row, repo.newUuid()), - ) - } - }, ) } composable(Routes.MY_VEHICLES) { @@ -521,6 +591,21 @@ fun BigFredApp() { } } } + + if (localStartError != null) { + AlertDialog( + onDismissRequest = { localStartError = null }, + title = { Text(stringResource(R.string.discovery_on_phone_title)) }, + text = { + Text(stringResource(R.string.discovery_on_phone_failed, localStartError!!)) + }, + confirmButton = { + TextButton(onClick = { localStartError = null }) { + Text(stringResource(R.string.discovery_on_phone_cancel)) + } + }, + ) + } } @Composable diff --git a/app/src/main/java/com/dccbigfred/android/ui/navigation/Routes.kt b/app/src/main/java/com/dccbigfred/android/ui/navigation/Routes.kt index b7b448c..9d0d0db 100644 --- a/app/src/main/java/com/dccbigfred/android/ui/navigation/Routes.kt +++ b/app/src/main/java/com/dccbigfred/android/ui/navigation/Routes.kt @@ -9,4 +9,5 @@ object Routes { const val MODELS = "models" const val MY_VEHICLES = "my_vehicles" const val ABOUT = "about" + const val LOCAL_SERVER = "local_server" } diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 4b41566..4559646 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -56,6 +56,12 @@ Zu meinen Fahrzeugen auf dem Telefon hinzugefügt Fahrzeugnummer im Internet suchen Modell im Internet suchen + So funktioniert es + So nutzen Sie den Modellkatalog + Finden Sie ein Fahrzeug im Katalog + Halten Sie lange und wählen Sie „Zu meinen Fahrzeugen hinzufügen“ + Anschließend können Sie auf dem Bildschirm „Meine Fahrzeuge auf dem Telefon“ das Fahrzeug bearbeiten, benennen und die DCC-Adresse festlegen + Schließlich können Sie das Fahrzeug an einen Heim- oder Club-BigFred-Server senden, nachdem Sie sich angemeldet haben Meine Fahrzeuge auf dem Telefon @@ -98,6 +104,18 @@ Ausgewählte senden (%1$d) %1$s: %2$s Schließen + So funktioniert es + So nutzen Sie Meine Fahrzeuge auf dem Telefon + Bereiten Sie Ihre Fahrzeuge zu Hause, vor einer Veranstaltung oder jederzeit auf Ihrem Telefon vor — tippen Sie auf dem Bildschirm „Meine Fahrzeuge auf dem Telefon“ auf „Fahrzeug hinzufügen“, oder halten Sie auf dem Bildschirm „Modellkatalog“ lange auf ein Modell und wählen Sie „Zu meinen Fahrzeugen hinzufügen“. + Ergänzen Sie Name, DCC-Adresse und weitere Details, indem Sie lange auf ein Fahrzeug drücken und „Bearbeiten“ wählen. + Wenn Sie in Reichweite eines BigFred-Hubs sind (zum Beispiel auf einer Veranstaltung oder zu Hause), melden Sie sich mit dem Benutzernamen und der PIN an, die Sie von den Organisatoren erhalten oder selbst vergeben haben. + Markieren Sie Fahrzeuge unter „Meine Fahrzeuge auf dem Telefon“ und tippen Sie auf „Ausgewählte senden“, um sie an den verbundenen BigFred-Hub zu senden. + Kehren Sie dann zur „BigFred-Anwendung“ zurück, öffnen Sie den Tab „Meine“ → „Fahrzeuge“ und tippen Sie auf das Plus, um ein Fahrzeug zur Anlage hinzuzufügen. + Fertig! Sie können losfahren. + Auf dem Telefon gespeichert! Sie sind derzeit mit BigFred verbunden. Möchten Sie das Fahrzeug an den Server senden? + Später + Ja + Wenn Sie sich mit BigFred verbinden, können Sie Ihr Fahrzeug auf den Server kopieren BigFred-Server wählen @@ -114,6 +132,30 @@ Subnetz .120 manuell + + BigFred auf dem Telefon + BigFred auf dem Telefon (aktiv) + Vereinfachter Modus + Achtung: BigFred auf dem Telefon ist nur eine VEREINFACHTE Anwendung, mit der sich kein zweites Telefon, keine ROCO WlanMaus, kein WiFred und kein LongFred verbinden kann. Verwenden Sie BigFred im vereinfachten Modus nicht gleichzeitig auf mehreren Telefonen mit derselben Zentrale. Für mehrere Personen/Telefone/Handregler starten Sie BigFred auf einem Raspberry Pi 4/5+ + Weiter + Abbrechen + BigFred wird auf diesem Telefon gestartet… + Lokales BigFred konnte nicht gestartet werden: %1$s + BigFred auf dem Telefon: aktiv + BigFred auf dem Telefon + Server stoppen + Server neu starten + BigFred öffnen + Aktuelle Protokolle + Gestoppt + Startet… + Läuft unter %1$s + Fehler: %1$s + Lokaler BigFred-Server + BigFred auf dem Telefon + Vereinfachte Zentrale läuft + Native Binärdateien fehlen. Valkey/supervisord-Releases holen (make native-prebuilt) und loco-server-android-arm64 bereitstellen (make android in ../bigfred oder bigfredNativeTag setzen). + Einstellungen Sprache diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 2f4271f..16f703f 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -56,6 +56,12 @@ Dodano do moich pojazdów na telefonie Wyszukaj numer pojazdu w internecie Wyszukaj model w internecie + Jak korzystać + Jak korzystać z Bazy modeli + Znajdź pojazd w katalogu + Przetrzymaj palcem i wybierz „Dodaj do moich pojazdów” + Następnie na ekranie „Moje pojazdy na telefonie” możesz edytować pojazd, nazwać go, ustawić adres DCC + Finalnie pojazd możesz wysłać na serwer domowy lub klubowy BigFred po tym jak się do niego zalogujesz Moje pojazdy na telefonie @@ -98,6 +104,18 @@ Wyślij zaznaczone (%1$d) %1$s: %2$s Zamknij + Jak korzystać + Jak korzystać z Moje pojazdy na telefonie + Przygotuj swoje pojazdy w domu, przed imprezą, kiedykolwiek na swoim telefonie — klikając „Dodaj pojazd” na ekranie „Moje pojazdy na telefonie”, lub będąc na ekranie „Baza modeli” przetrzymaj palec na modelu i wybierz „Dodaj do moich pojazdów”. + Uzupełnij nazwę pojazdu, adres DCC i inne szczegóły trzymając palec na pojeździe i wybierając „Edytuj”. + Gdy będziesz w zasięgu huba BigFred (na przykład na imprezie lub w domu), zaloguj się do niego swoim loginem i PIN-em jaki dostałeś od organizatorów/ek lub jaki sobie sam nadałeś/aś. + Zaznacz pojazdy w „Moje pojazdy na telefonie” i kliknij „Wyślij zaznaczone”, aby wysłać do huba BigFred, do którego się podłączyłeś/aś. + Następnie wróć do „Aplikacja BigFred”, wejdź do zakładki „Moje” → „Pojazdy” i kliknij plusik, aby dodać pojazd do makiety. + Gotowe! Już możesz jeździć! + Zapisano na telefonie! Jesteś podłączony do BigFred obecnie. Czy chcesz wysłać pojazd na serwer? + Później + Tak + Gdy połączysz się z BigFred możesz skopiować swój pojazd na serwer Wybierz serwer BigFred @@ -114,6 +132,30 @@ podsieć .120 ręczny + + BigFred na telefonie + BigFred na telefonie (działa) + Tryb uproszczony + Uwaga: BigFred uruchomiony na telefonie jest jedynie UPROSZCZONĄ aplikacją, do której nie połączy się drugi telefon, ROCO WlanMaus, WiFred ani LongFred. Nie używaj BigFreda w trybie uproszczonym na wielu telefonach jednocześnie z tą samą centralką. Aby używać BigFred przez wiele osób/telefonów/pilotów uruchom go na Raspberry Pi 4/5+ + Kontynuuj + Anuluj + Uruchamianie BigFred na tym telefonie… + Nie udało się uruchomić lokalnego BigFred: %1$s + BigFred na telefonie: działa + BigFred na telefonie + Zatrzymaj serwer + Restartuj serwer + Otwórz BigFred + Ostatnie logi + Zatrzymany + Uruchamianie… + Działa pod %1$s + Błąd: %1$s + Lokalny serwer BigFred + BigFred na telefonie + Uproszczona centralka jest uruchomiona + Brak binarek natywnych. Pobierz release Valkey/supervisord (make native-prebuilt) i dostarcz loco-server-android-arm64 (make android w ../bigfred lub ustaw bigfredNativeTag). + Ustawienia Język diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3c45c6c..a6f18a5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -56,6 +56,12 @@ Added to my vehicles on phone Search vehicle number online Search model online + How to use + How to use the Model catalog + Find a vehicle in the catalog + Long-press and choose “Add to my vehicles” + Then on the “My vehicles on phone” screen you can edit the vehicle, name it, and set the DCC address + Finally, you can send the vehicle to a home or club BigFred server after logging in My vehicles on phone @@ -98,6 +104,18 @@ Send selected (%1$d) %1$s: %2$s Dismiss + How to use + How to use My vehicles on phone + Prepare your vehicles at home, before an event, or whenever on your phone — tap “Add vehicle” on the “My vehicles on phone” screen, or on the “Model catalog” screen long-press a model and choose “Add to my vehicles”. + Fill in the vehicle name, DCC address and other details by long-pressing a vehicle and choosing “Edit”. + When you are in range of a BigFred hub (for example at an event or at home), log in with the username and PIN you received from the organizers or set yourself. + Select vehicles in “My vehicles on phone” and tap “Send selected” to send them to the BigFred hub you are connected to. + Then go back to “BigFred application”, open the “Mine” → “Vehicles” tab and tap the plus button to add a vehicle to the layout. + Done! You can start driving. + Saved on phone! You are currently connected to BigFred. Do you want to send the vehicle to the server? + Later + Yes + When you connect to BigFred you can copy your vehicle to the server Choose BigFred server @@ -114,6 +132,30 @@ subnet .120 manual + + BigFred on phone + BigFred on phone (running) + Simplified mode + Warning: BigFred running on a phone is only a SIMPLIFIED application that a second phone, ROCO WlanMaus, WiFred or LongFred cannot connect to. Do not use BigFred in simplified mode on multiple phones at the same time with the same command station. To use BigFred with multiple people/phones/controllers, run it on a Raspberry Pi 4/5+ + Continue + Cancel + Starting BigFred on this phone… + Failed to start local BigFred: %1$s + BigFred on phone: running + BigFred on phone + Stop server + Restart server + Open BigFred + Recent logs + Stopped + Starting… + Running at %1$s + Failed: %1$s + Local BigFred server + BigFred on phone + Simplified command station is running + Native binaries missing. Fetch Valkey/supervisord releases (make native-prebuilt) and provide loco-server-android-arm64 (make android in ../bigfred or set bigfredNativeTag). + Settings Language diff --git a/app/src/test/java/com/dccbigfred/android/server/ProcessOrphanReaperTest.kt b/app/src/test/java/com/dccbigfred/android/server/ProcessOrphanReaperTest.kt new file mode 100644 index 0000000..4e021ae --- /dev/null +++ b/app/src/test/java/com/dccbigfred/android/server/ProcessOrphanReaperTest.kt @@ -0,0 +1,31 @@ +package com.dccbigfred.android.server + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import java.io.File + +class ProcessOrphanReaperTest { + @Test + fun readPid_missingFile_returnsNull() { + val f = File.createTempFile("pid", ".txt") + f.delete() + assertTrue(ProcessOrphanReaper.readPid(f) == null) + } + + @Test + fun writeAndReadPid_roundTrip() { + val f = File.createTempFile("pid", ".txt") + f.deleteOnExit() + ProcessOrphanReaper.writePid(f, 4242) + assertTrue(ProcessOrphanReaper.readPid(f) == 4242) + } + + @Test + fun isLocalUrl_detectsLoopback() { + assertTrue(LocoServerService.isLocalUrl("http://127.0.0.1:8080")) + assertTrue(LocoServerService.isLocalUrl("http://localhost:8080")) + assertFalse(LocoServerService.isLocalUrl("http://192.168.0.120:8080")) + assertFalse(LocoServerService.isLocalUrl(null)) + } +} diff --git a/gradle.properties b/gradle.properties index f0a2e55..38eb1f2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,3 +2,13 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 android.useAndroidX=true kotlin.code.style=official android.nonTransitiveRClass=true + +# Native loco-server for "BigFred on phone" (GitHub release of bigfred repo). +# Prefer local ../bigfred/bin/loco-server-android-arm64 when present. +# Example: bigfredNativeRepo=owner/bigfred bigfredNativeTag=v1.2.3 +bigfredNativeRepo= +bigfredNativeTag= + +# Valkey / supervisord Android prebuilts (latest GitHub release of each deps repo). +depsAndroidValkeyRepo=dcc-bigfred/deps-android-valkey +depsAndroidSupervisordRepo=dcc-bigfred/deps-android-supervisord diff --git a/scripts/fetch-github-release-asset.sh b/scripts/fetch-github-release-asset.sh new file mode 100755 index 0000000..4d817a2 --- /dev/null +++ b/scripts/fetch-github-release-asset.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Download a single asset from the latest GitHub release of a repo. +# Usage: ./scripts/fetch-github-release-asset.sh +# +# Auth (optional, needed for private repos): +# GITHUB_TOKEN / GH_TOKEN / BIGFRED_NATIVE_TOKEN +set -euo pipefail + +REPO="${1:?usage: $0 }" +ASSET="${2:?}" +OUT="${3:?}" + +mkdir -p "$(dirname "${OUT}")" + +TOKEN="${BIGFRED_NATIVE_TOKEN:-${GH_TOKEN:-${GITHUB_TOKEN:-}}}" +AUTH=() +if [[ -n "${TOKEN}" ]]; then + AUTH=(-H "Authorization: Bearer ${TOKEN}") +fi + +API="https://api.github.com/repos/${REPO}/releases/latest" +echo "Resolving latest release of ${REPO}…" +json="$(curl -fsSL "${AUTH[@]}" -H "Accept: application/vnd.github+json" "${API}")" + +tag="$(printf '%s' "${json}" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("tag_name",""))')" +url="$(printf '%s' "${json}" | python3 -c ' +import json,sys +asset=sys.argv[1] +data=json.load(sys.stdin) +for a in data.get("assets") or []: + if a.get("name")==asset: + print(a.get("url") or "") + break +' "${ASSET}")" + +if [[ -z "${url}" ]]; then + echo "error: asset '${ASSET}' not found in latest release of ${REPO} (tag=${tag:-unknown})" >&2 + echo "Available assets:" >&2 + printf '%s' "${json}" | python3 -c 'import json,sys; [print(" -",a.get("name")) for a in (json.load(sys.stdin).get("assets") or [])]' >&2 + exit 1 +fi + +echo "Downloading ${ASSET} from ${REPO}@${tag}" +tmp="$(mktemp)" +cleanup() { rm -f "${tmp}"; } +trap cleanup EXIT + +# Asset API URL requires Accept: application/octet-stream +curl -fsSL "${AUTH[@]}" \ + -H "Accept: application/octet-stream" \ + -L \ + -o "${tmp}" \ + "${url}" + +cp -f "${tmp}" "${OUT}" +chmod 755 "${OUT}" +echo "Wrote ${OUT} ($(wc -c < "${OUT}") bytes) from ${REPO}@${tag}" From 7fae1463b8400df15295b76c0cbfab22cb3064d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20K=C4=99ska?= <372403+keskad@users.noreply.github.com> Date: Wed, 29 Jul 2026 09:54:52 +0200 Subject: [PATCH 02/17] fix(android): surface local-server start failures and show progress Failed boot previously reset state to Stopped, so Discovery looked like a no-op. Preserve Failed, navigate to status while starting, and request notification permission. Co-authored-by: Cursor --- .../android/server/LocoServerService.kt | 48 ++++++++++++--- .../ui/localserver/LocalServerStatusScreen.kt | 61 +++++++++++++++---- .../android/ui/navigation/BigFredApp.kt | 18 +++++- 3 files changed, 106 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt b/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt index d1b3125..7dd82c8 100644 --- a/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt +++ b/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt @@ -49,15 +49,25 @@ class LocoServerService : Service() { } ACTION_START, null -> { if (!running.get()) { - startForegroundNotification() + _state.value = LocalServerState.Starting + try { + startForegroundNotification() + } catch (e: Exception) { + Log.e(TAG, "startForeground failed", e) + _state.value = LocalServerState.Failed(e.message ?: e.toString()) + stopSelf() + return START_NOT_STICKY + } acquireWakeLock() thread(name = "loco-server-boot", isDaemon = true) { try { boot() } catch (e: Exception) { Log.e(TAG, "local server start failed", e) + cleanupChildren() + releaseWakeLock() + // Must set Failed after cleanup — stopLocalServer() would clear it to Stopped. _state.value = LocalServerState.Failed(e.message ?: e.toString()) - stopLocalServer() stopSelf() } } @@ -68,8 +78,15 @@ class LocoServerService : Service() { } override fun onDestroy() { - stopLocalServer() + cleanupChildren() releaseWakeLock() + // Keep Failed visible so the UI can show the error after the service exits. + if (_state.value !is LocalServerState.Failed) { + _state.value = LocalServerState.Stopped + } + running.set(false) + watchdogThread?.interrupt() + watchdogThread = null super.onDestroy() } @@ -191,7 +208,7 @@ class LocoServerService : Service() { val valkeyOk = supervised || valkeyProcess?.isAlive == true if (!locoAlive || !valkeyOk) { Log.w(TAG, "child died loco=$locoAlive valkeyOk=$valkeyOk — restarting") - stopChildren() + cleanupChildren() running.set(false) boot() return@thread @@ -213,7 +230,7 @@ class LocoServerService : Service() { running.set(false) watchdogThread?.interrupt() watchdogThread = null - stopChildren() + cleanupChildren() val paths = LocalServerPaths.from(this) paths.locoServerPid.delete() paths.valkeyPid.delete() @@ -221,7 +238,7 @@ class LocoServerService : Service() { _state.value = LocalServerState.Stopped } - private fun stopChildren() { + private fun cleanupChildren() { listOf(locoProcess, valkeyProcess).forEach { proc -> proc ?: return@forEach try { @@ -234,6 +251,13 @@ class LocoServerService : Service() { } locoProcess = null valkeyProcess = null + try { + val paths = LocalServerPaths.from(this) + paths.locoServerPid.delete() + paths.valkeyPid.delete() + paths.supervisordPid.delete() + } catch (_: Exception) { + } } /** Copy supervisord/ctl into codeCacheDir/bin so LookPath finds bare names. */ @@ -337,8 +361,18 @@ class LocoServerService : Service() { val state: StateFlow = _state.asStateFlow() fun start(context: Context) { + if (_state.value !is LocalServerState.Running && + _state.value !is LocalServerState.Starting + ) { + _state.value = LocalServerState.Starting + } val intent = Intent(context, LocoServerService::class.java).setAction(ACTION_START) - context.startForegroundService(intent) + try { + context.startForegroundService(intent) + } catch (e: Exception) { + Log.e(TAG, "startForegroundService failed", e) + _state.value = LocalServerState.Failed(e.message ?: e.toString()) + } } fun stop(context: Context) { diff --git a/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerStatusScreen.kt b/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerStatusScreen.kt index 25a557b..cae7166 100644 --- a/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerStatusScreen.kt +++ b/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerStatusScreen.kt @@ -2,14 +2,18 @@ package com.dccbigfred.android.ui.localserver import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton @@ -17,8 +21,12 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -30,6 +38,7 @@ import com.dccbigfred.android.server.LocalServerState import com.dccbigfred.android.server.LocoServerService import com.dccbigfred.android.ui.components.topAppBarEdgePadding import java.io.File +import kotlinx.coroutines.delay @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -39,9 +48,19 @@ fun LocalServerStatusScreen( ) { val context = LocalContext.current val state by LocoServerService.state.collectAsStateWithLifecycle() - val logs = remember(state) { + var logTick by remember { mutableIntStateOf(0) } + LaunchedEffect(state) { + if (state is LocalServerState.Starting) { + while (LocoServerService.state.value is LocalServerState.Starting) { + delay(1_000) + logTick++ + } + } + logTick++ + } + val logs = remember(state, logTick) { val dir = LocalServerPaths.from(context).logsDir - listOf("loco-server.log", "valkey.log") + listOf("loco-server.log", "valkey.log", "supervisord.log") .map { File(dir, it) } .filter { it.isFile } .joinToString("\n\n") { f -> @@ -66,17 +85,33 @@ fun LocalServerStatusScreen( .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(12.dp), ) { - Text( - text = when (val s = state) { - LocalServerState.Stopped -> stringResource(R.string.local_server_status_stopped) - LocalServerState.Starting -> stringResource(R.string.local_server_status_starting) - is LocalServerState.Running -> - stringResource(R.string.local_server_status_running, s.baseUrl) - is LocalServerState.Failed -> - stringResource(R.string.local_server_status_failed, s.message) - }, - style = MaterialTheme.typography.bodyLarge, - ) + if (state is LocalServerState.Starting) { + Row(verticalAlignment = Alignment.CenterVertically) { + CircularProgressIndicator(modifier = Modifier.size(22.dp), strokeWidth = 2.dp) + Spacer(modifier = Modifier.width(12.dp)) + Text( + text = stringResource(R.string.discovery_on_phone_starting), + style = MaterialTheme.typography.bodyLarge, + ) + } + } else { + Text( + text = when (val s = state) { + LocalServerState.Stopped -> stringResource(R.string.local_server_status_stopped) + LocalServerState.Starting -> stringResource(R.string.local_server_status_starting) + is LocalServerState.Running -> + stringResource(R.string.local_server_status_running, s.baseUrl) + is LocalServerState.Failed -> + stringResource(R.string.local_server_status_failed, s.message) + }, + style = MaterialTheme.typography.bodyLarge, + color = if (state is LocalServerState.Failed) { + MaterialTheme.colorScheme.error + } else { + MaterialTheme.colorScheme.onSurface + }, + ) + } if (state is LocalServerState.Running) { Button( diff --git a/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt b/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt index 30983cf..fcb27b1 100644 --- a/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt +++ b/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt @@ -225,11 +225,27 @@ fun BigFredApp() { val localServerState by LocoServerService.state.collectAsStateWithLifecycle() var localStartError by remember { mutableStateOf(null) } + val notificationPermissionLauncher = androidx.activity.compose.rememberLauncherForActivityResult( + contract = androidx.activity.result.contract.ActivityResultContracts.RequestPermission(), + ) { /* proceed regardless — FGS may still start with silent notifications denied */ } + fun startLocalServer() { scope.launch { localStartError = null + if (android.os.Build.VERSION.SDK_INT >= 33) { + val granted = androidx.core.content.ContextCompat.checkSelfPermission( + context, + android.Manifest.permission.POST_NOTIFICATIONS, + ) == android.content.pm.PackageManager.PERMISSION_GRANTED + if (!granted) { + notificationPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS) + } + } + // Status screen shows Starting / Failed / Running while boot runs. + navController.navigate(Routes.LOCAL_SERVER) { + launchSingleTop = true + } LocoServerService.start(context) - // Wait until running or failed (bounded). val deadline = System.currentTimeMillis() + 60_000 while (System.currentTimeMillis() < deadline) { when (val s = LocoServerService.state.value) { From 54910f89b074e89947ae0ca2107e176778a82d29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20K=C4=99ska?= <372403+keskad@users.noreply.github.com> Date: Wed, 29 Jul 2026 10:11:52 +0200 Subject: [PATCH 03/17] fix(android): require libloco-server.so and build it in CI from bigfred Co-authored-by: Cursor --- .github/workflows/ci.yml | 48 +++++++++++++++++++++++++ .gitignore | 3 ++ app/build.gradle.kts | 76 +++++++++++++++++++++++++--------------- gradle.properties | 9 ++--- 4 files changed, 103 insertions(+), 33 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1cac84..b02ce59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,6 +76,45 @@ jobs: echo "$BIGFRED_STORE_FILE_CONTENT" | base64 -d > "${RUNNER_TEMP}/bigfred-release.jks" echo "BIGFRED_STORE_FILE=${RUNNER_TEMP}/bigfred-release.jks" >> "$GITHUB_ENV" + - name: Set up Go (loco-server-android-arm64) + uses: actions/setup-go@v5 + with: + go-version: "1.25.0" + cache: false + + - name: Set up Node (loco-server SPA embed) + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Checkout bigfred for loco-server-android-arm64 + uses: actions/checkout@v4 + with: + repository: dcc-bigfred/bigfred + # Prefer repo variable BIGFRED_REF; default to phone branch until merged to master. + ref: ${{ vars.BIGFRED_REF || 'feat/bigfred-on-phone' }} + path: .deps/bigfred + + - name: Build loco-server-android-arm64 + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y librsvg2-bin + make -C .deps/bigfred android + mkdir -p native-prebuilt/arm64-v8a + cp -f .deps/bigfred/bin/loco-server-android-arm64 \ + native-prebuilt/arm64-v8a/libloco-server.so + chmod 755 native-prebuilt/arm64-v8a/libloco-server.so + ls -lh native-prebuilt/arm64-v8a/libloco-server.so + + - name: Fetch Valkey / supervisord prebuilts + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + make valkey-android supervisord-android + ls -lh native-prebuilt/arm64-v8a/ + - name: Unit tests env: GRADLE_FLAGS: "" @@ -87,8 +126,17 @@ jobs: BIGFRED_STORE_PASSWORD: ${{ secrets.BIGFRED_STORE_PASSWORD }} BIGFRED_KEY_ALIAS: ${{ secrets.BIGFRED_KEY_ALIAS }} BIGFRED_KEY_PASSWORD: ${{ secrets.BIGFRED_KEY_PASSWORD }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: make apk + - name: Verify APK contains loco-server + run: | + set -euo pipefail + APK="app/build/outputs/apk/release/app-release.apk" + unzip -l "$APK" | grep -E 'lib/arm64-v8a/libloco-server\.so' + unzip -l "$APK" | grep -E 'lib/arm64-v8a/libvalkey-server\.so' + unzip -l "$APK" | grep -E 'lib/arm64-v8a/libsupervisord\.so' + - name: Stage APK artifact run: | set -euo pipefail diff --git a/.gitignore b/.gitignore index 09c2766..a33720d 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,6 @@ tools/hydrus-import/__pycache__/ # Native prebuilts (fetched from deps-android-* GitHub Releases) and staged jniLibs native-prebuilt/ app/src/main/jniLibs/ + +# CI / local checkout of dcc-bigfred/bigfred for loco-server-android-arm64 +.deps/ diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0abef76..14af9a2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -179,39 +179,57 @@ val fetchNativeBinaries by tasks.registering { "dcc-bigfred/deps-android-supervisord", ) + // loco-server: local ../bigfred/bin → native-prebuilt → GitHub release asset. val locoDest = File(outDir, "libloco-server.so") - if (localLoco.asFile.isFile) { - localLoco.asFile.copyTo(locoDest, overwrite = true) - println("Staged libloco-server.so from local ${localLoco.asFile}") - } else { - val repo = (project.findProperty("bigfredNativeRepo") as String?) - ?.ifBlank { null } - ?: System.getenv("BIGFRED_NATIVE_REPO") - val tag = (project.findProperty("bigfredNativeTag") as String?) - ?.ifBlank { null } - ?: System.getenv("BIGFRED_NATIVE_TAG") - if (repo.isNullOrBlank() || tag.isNullOrBlank()) { - logger.warn( - "libloco-server.so missing: build ../bigfred with 'make android' " + - "or set bigfredNativeRepo + bigfredNativeTag", - ) - } else { - val token = githubToken() - val url = - "https://github.com/$repo/releases/download/$tag/loco-server-android-arm64" - val tmp = File(outDir, ".loco-server.download") - val conn = URI(url).toURL().openConnection() as HttpURLConnection - if (!token.isNullOrBlank()) { - conn.setRequestProperty("Authorization", "Bearer $token") - conn.setRequestProperty("Accept", "application/octet-stream") - } - conn.inputStream.use { input -> - tmp.outputStream().use { output -> input.copyTo(output) } + val locoCached = prebuilt.file("libloco-server.so").asFile + when { + localLoco.asFile.isFile -> { + localLoco.asFile.copyTo(locoDest, overwrite = true) + println("Staged libloco-server.so from local ${localLoco.asFile}") + } + locoCached.isFile -> { + locoCached.copyTo(locoDest, overwrite = true) + println("Staged libloco-server.so from ${locoCached.absolutePath}") + } + else -> { + val repo = (project.findProperty("bigfredNativeRepo") as String?) + ?.ifBlank { null } + ?: System.getenv("BIGFRED_NATIVE_REPO") + ?: "dcc-bigfred/bigfred" + val tag = (project.findProperty("bigfredNativeTag") as String?) + ?.ifBlank { null } + ?: System.getenv("BIGFRED_NATIVE_TAG") + if (!tag.isNullOrBlank()) { + val token = githubToken() + val url = + "https://github.com/$repo/releases/download/$tag/loco-server-android-arm64" + val tmp = File(outDir, ".loco-server.download") + val conn = URI(url).toURL().openConnection() as HttpURLConnection + if (!token.isNullOrBlank()) { + conn.setRequestProperty("Authorization", "Bearer $token") + conn.setRequestProperty("Accept", "application/octet-stream") + } + conn.inputStream.use { input -> + tmp.outputStream().use { output -> input.copyTo(output) } + } + tmp.renameTo(locoDest) + println("Downloaded libloco-server.so from $url") + } else { + // Latest release asset (name differs from jni lib name). + fetchLatestReleaseAsset(repo, "loco-server-android-arm64", locoDest) } - tmp.renameTo(locoDest) - println("Downloaded libloco-server.so from $url") + locoDest.parentFile.mkdirs() + locoCached.parentFile.mkdirs() + locoDest.copyTo(locoCached, overwrite = true) } } + if (!locoDest.isFile || locoDest.length() < 1024) { + throw GradleException( + "libloco-server.so missing after fetch. " + + "Build with 'make -C ../bigfred android', place it in native-prebuilt/arm64-v8a/, " + + "or publish loco-server-android-arm64 on dcc-bigfred/bigfred releases.", + ) + } } } diff --git a/gradle.properties b/gradle.properties index 38eb1f2..78dbb2a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,10 +3,11 @@ android.useAndroidX=true kotlin.code.style=official android.nonTransitiveRClass=true -# Native loco-server for "BigFred on phone" (GitHub release of bigfred repo). -# Prefer local ../bigfred/bin/loco-server-android-arm64 when present. -# Example: bigfredNativeRepo=owner/bigfred bigfredNativeTag=v1.2.3 -bigfredNativeRepo= +# Native loco-server for "BigFred on phone". +# Prefer local ../bigfred/bin/loco-server-android-arm64, then native-prebuilt/, +# then latest GitHub release asset loco-server-android-arm64 from this repo. +# Optional pin: bigfredNativeTag=v1.2.3 +bigfredNativeRepo=dcc-bigfred/bigfred bigfredNativeTag= # Valkey / supervisord Android prebuilts (latest GitHub release of each deps repo). From 5a310f9c3de1b33e298c747f6b63b21d90b21e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20K=C4=99ska?= <372403+keskad@users.noreply.github.com> Date: Wed, 29 Jul 2026 10:34:00 +0200 Subject: [PATCH 04/17] Pull loco-server from GHCR (ORAS) instead of building bigfred in CI. Replace checkout+make android with oras pull from ghcr.io/dcc-bigfred/ loco-server-android-arm64; match branch OCI tag with fallback to main. Co-authored-by: Cursor --- .github/workflows/ci.yml | 48 +++++++------------- .gitignore | 5 +-- Makefile | 24 +++++++--- app/build.gradle.kts | 59 ++++++++++-------------- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- gradle.properties | 9 ++-- scripts/fetch-ghcr-oras.sh | 62 ++++++++++++++++++++++++++ 9 files changed, 128 insertions(+), 85 deletions(-) create mode 100755 scripts/fetch-ghcr-oras.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b02ce59..cc043c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ on: permissions: contents: read + packages: read jobs: test-and-build: @@ -76,43 +77,26 @@ jobs: echo "$BIGFRED_STORE_FILE_CONTENT" | base64 -d > "${RUNNER_TEMP}/bigfred-release.jks" echo "BIGFRED_STORE_FILE=${RUNNER_TEMP}/bigfred-release.jks" >> "$GITHUB_ENV" - - name: Set up Go (loco-server-android-arm64) - uses: actions/setup-go@v5 - with: - go-version: "1.25.0" - cache: false - - - name: Set up Node (loco-server SPA embed) - uses: actions/setup-node@v4 - with: - node-version: "20" - - - name: Checkout bigfred for loco-server-android-arm64 - uses: actions/checkout@v4 - with: - repository: dcc-bigfred/bigfred - # Prefer repo variable BIGFRED_REF; default to phone branch until merged to master. - ref: ${{ vars.BIGFRED_REF || 'feat/bigfred-on-phone' }} - path: .deps/bigfred + - name: Set up ORAS + uses: oras-project/setup-oras@v1 - - name: Build loco-server-android-arm64 - run: | - set -euo pipefail - sudo apt-get update - sudo apt-get install -y librsvg2-bin - make -C .deps/bigfred android - mkdir -p native-prebuilt/arm64-v8a - cp -f .deps/bigfred/bin/loco-server-android-arm64 \ - native-prebuilt/arm64-v8a/libloco-server.so - chmod 755 native-prebuilt/arm64-v8a/libloco-server.so - ls -lh native-prebuilt/arm64-v8a/libloco-server.so - - - name: Fetch Valkey / supervisord prebuilts + - name: Fetch native prebuilts env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BIGFRED_OCI_TAG: ${{ vars.BIGFRED_OCI_TAG }} run: | set -euo pipefail - make valkey-android supervisord-android + if [ -n "${BIGFRED_OCI_TAG:-}" ]; then + TAG="${BIGFRED_OCI_TAG}" + elif [ -n "${GITHUB_HEAD_REF:-}" ]; then + TAG="branch-$(echo "${GITHUB_HEAD_REF}" | tr '/_' '-' | tr '[:upper:]' '[:lower:]')" + elif [ "${GITHUB_REF_NAME}" = "master" ] || [ "${GITHUB_REF_NAME}" = "main" ]; then + TAG="main" + else + TAG="branch-$(echo "${GITHUB_REF_NAME}" | tr '/_' '-' | tr '[:upper:]' '[:lower:]')" + fi + echo "Using loco-server OCI tag: ${TAG} (fallback: main)" + make native-prebuilt BIGFRED_OCI_TAG="${TAG}" ls -lh native-prebuilt/arm64-v8a/ - name: Unit tests diff --git a/.gitignore b/.gitignore index a33720d..c3c85f8 100644 --- a/.gitignore +++ b/.gitignore @@ -38,9 +38,6 @@ app/src/main/assets/models/ tools/hydrus-import/out/ tools/hydrus-import/__pycache__/ -# Native prebuilts (fetched from deps-android-* GitHub Releases) and staged jniLibs +# Native prebuilts (GHCR + deps-android-* GitHub Releases) and staged jniLibs native-prebuilt/ app/src/main/jniLibs/ - -# CI / local checkout of dcc-bigfred/bigfred for loco-server-android-arm64 -.deps/ diff --git a/Makefile b/Makefile index 0d5f46c..9081c7a 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,8 @@ # make debug — debug APK # make clean — remove build outputs # make import-models — fetch hydrus.pl catalog into assets/models/ -# make valkey-android — download libvalkey-server.so from deps-android-valkey latest release +# make loco-android — download libloco-server.so from GHCR (ORAS) +# make valkey-android — download libvalkey-server.so from deps-android-valkey latest release # make supervisord-android — download supervisord libs from deps-android-supervisord latest release GRADLE ?= ./gradlew @@ -20,15 +21,19 @@ IMPORT_OUT := tools/hydrus-import/out ASSETS_MODELS := app/src/main/assets/models NATIVE_PREBUILT := native-prebuilt/arm64-v8a +LOCO_SO := $(NATIVE_PREBUILT)/libloco-server.so VALKEY_SO := $(NATIVE_PREBUILT)/libvalkey-server.so SUPERVISORD_SO := $(NATIVE_PREBUILT)/libsupervisord.so SUPERVISORCTL_SO := $(NATIVE_PREBUILT)/libsupervisorctl.so +BIGFRED_OCI_IMAGE ?= ghcr.io/dcc-bigfred/loco-server-android-arm64 +BIGFRED_OCI_TAG ?= main + VALKEY_REPO ?= dcc-bigfred/deps-android-valkey SUPERVISORD_REPO ?= dcc-bigfred/deps-android-supervisord .PHONY: help apk release test test-android debug clean import-models \ - valkey-android supervisord-android native-prebuilt + loco-android valkey-android supervisord-android native-prebuilt help: @echo "Targets:" @@ -38,6 +43,7 @@ help: @echo " make test-android Run instrumented tests (device/emulator required)" @echo " make debug Build debug APK → $(APK_DEBUG)" @echo " make import-models Import hydrus models DB + thumbs → $(ASSETS_MODELS)" + @echo " make loco-android Fetch $(LOCO_SO) from $(BIGFRED_OCI_IMAGE):$(BIGFRED_OCI_TAG) (skip if exists; FORCE=1)" @echo " make valkey-android Fetch $(VALKEY_SO) from $(VALKEY_REPO) latest release (skip if exists; FORCE=1)" @echo " make supervisord-android Fetch supervisord libs from $(SUPERVISORD_REPO) latest release (skip if exists; FORCE=1)" @echo " make clean Clean Gradle build outputs" @@ -64,11 +70,17 @@ import-models: ifdef FORCE .PHONY: force-clean-native force-clean-native: - rm -f "$(VALKEY_SO)" "$(SUPERVISORD_SO)" "$(SUPERVISORCTL_SO)" + rm -f "$(LOCO_SO)" "$(VALKEY_SO)" "$(SUPERVISORD_SO)" "$(SUPERVISORCTL_SO)" +loco-android: force-clean-native valkey-android: force-clean-native supervisord-android: force-clean-native endif +loco-android: $(LOCO_SO) + +$(LOCO_SO): + ./scripts/fetch-ghcr-oras.sh "$(BIGFRED_OCI_IMAGE)" "$(BIGFRED_OCI_TAG)" "$@" main + valkey-android: $(VALKEY_SO) $(VALKEY_SO): @@ -82,14 +94,14 @@ $(SUPERVISORD_SO): $(SUPERVISORCTL_SO): ./scripts/fetch-github-release-asset.sh "$(SUPERVISORD_REPO)" libsupervisorctl.so "$@" -native-prebuilt: valkey-android supervisord-android +native-prebuilt: loco-android valkey-android supervisord-android -apk release: valkey-android supervisord-android +apk release: native-prebuilt $(GRADLE) $(GRADLE_FLAGS) :app:assembleRelease @echo "APK: $(APK_RELEASE)" @ls -lh "$(APK_RELEASE)" -debug: valkey-android supervisord-android +debug: native-prebuilt $(GRADLE) $(GRADLE_FLAGS) :app:assembleDebug @echo "APK: $(APK_DEBUG)" @ls -lh "$(APK_DEBUG)" diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 14af9a2..c7bb441 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -6,8 +6,6 @@ plugins { } import java.io.File -import java.net.HttpURLConnection -import java.net.URI fun gitCommand(vararg args: String): String { return try { @@ -115,6 +113,7 @@ val fetchNativeBinaries by tasks.registering { val localLoco = rootProject.layout.projectDirectory.dir("../bigfred/bin") .file("loco-server-android-arm64") val fetchScript = rootProject.layout.projectDirectory.file("scripts/fetch-github-release-asset.sh") + val ghcrScript = rootProject.layout.projectDirectory.file("scripts/fetch-ghcr-oras.sh") outputs.dir(jniOut) doLast { val outDir = jniOut.asFile @@ -132,10 +131,9 @@ val fetchNativeBinaries by tasks.registering { return true } - fun fetchLatestReleaseAsset(repo: String, assetName: String, dest: File) { - val script = fetchScript.asFile - require(script.isFile) { "Missing fetch script: ${script.absolutePath}" } - val pb = ProcessBuilder(script.absolutePath, repo, assetName, dest.absolutePath) + fun runScript(script: File, vararg args: String) { + require(script.isFile) { "Missing script: ${script.absolutePath}" } + val pb = ProcessBuilder(listOf(script.absolutePath, *args)) pb.directory(rootProject.projectDir) pb.redirectErrorStream(true) val env = pb.environment() @@ -145,10 +143,26 @@ val fetchNativeBinaries by tasks.registering { val code = proc.waitFor() print(output) if (code != 0) { - throw GradleException("Failed to fetch $assetName from $repo (exit $code)") + throw GradleException("Script ${script.name} failed (exit $code)") } } + fun fetchLatestReleaseAsset(repo: String, assetName: String, dest: File) { + runScript(fetchScript.asFile, repo, assetName, dest.absolutePath) + } + + fun fetchLocoFromGhcr(dest: File) { + val image = (project.findProperty("bigfredOciImage") as String?) + ?.ifBlank { null } + ?: System.getenv("BIGFRED_OCI_IMAGE") + ?: "ghcr.io/dcc-bigfred/loco-server-android-arm64" + val tag = (project.findProperty("bigfredOciTag") as String?) + ?.ifBlank { null } + ?: System.getenv("BIGFRED_OCI_TAG") + ?: "main" + runScript(ghcrScript.asFile, image, tag, dest.absolutePath, "main") + } + fun stageOrFetch(prebuiltName: String, repoProp: String, defaultRepo: String) { val dest = File(outDir, prebuiltName) if (copyIfExists(prebuilt.file(prebuiltName).asFile, prebuiltName)) return @@ -179,7 +193,7 @@ val fetchNativeBinaries by tasks.registering { "dcc-bigfred/deps-android-supervisord", ) - // loco-server: local ../bigfred/bin → native-prebuilt → GitHub release asset. + // loco-server: local ../bigfred/bin → native-prebuilt → GHCR (ORAS). val locoDest = File(outDir, "libloco-server.so") val locoCached = prebuilt.file("libloco-server.so").asFile when { @@ -192,32 +206,7 @@ val fetchNativeBinaries by tasks.registering { println("Staged libloco-server.so from ${locoCached.absolutePath}") } else -> { - val repo = (project.findProperty("bigfredNativeRepo") as String?) - ?.ifBlank { null } - ?: System.getenv("BIGFRED_NATIVE_REPO") - ?: "dcc-bigfred/bigfred" - val tag = (project.findProperty("bigfredNativeTag") as String?) - ?.ifBlank { null } - ?: System.getenv("BIGFRED_NATIVE_TAG") - if (!tag.isNullOrBlank()) { - val token = githubToken() - val url = - "https://github.com/$repo/releases/download/$tag/loco-server-android-arm64" - val tmp = File(outDir, ".loco-server.download") - val conn = URI(url).toURL().openConnection() as HttpURLConnection - if (!token.isNullOrBlank()) { - conn.setRequestProperty("Authorization", "Bearer $token") - conn.setRequestProperty("Accept", "application/octet-stream") - } - conn.inputStream.use { input -> - tmp.outputStream().use { output -> input.copyTo(output) } - } - tmp.renameTo(locoDest) - println("Downloaded libloco-server.so from $url") - } else { - // Latest release asset (name differs from jni lib name). - fetchLatestReleaseAsset(repo, "loco-server-android-arm64", locoDest) - } + fetchLocoFromGhcr(locoDest) locoDest.parentFile.mkdirs() locoCached.parentFile.mkdirs() locoDest.copyTo(locoCached, overwrite = true) @@ -227,7 +216,7 @@ val fetchNativeBinaries by tasks.registering { throw GradleException( "libloco-server.so missing after fetch. " + "Build with 'make -C ../bigfred android', place it in native-prebuilt/arm64-v8a/, " + - "or publish loco-server-android-arm64 on dcc-bigfred/bigfred releases.", + "or pull from ghcr.io/dcc-bigfred/loco-server-android-arm64 (ORAS).", ) } } diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 4559646..cf4dcd5 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -154,7 +154,7 @@ Lokaler BigFred-Server BigFred auf dem Telefon Vereinfachte Zentrale läuft - Native Binärdateien fehlen. Valkey/supervisord-Releases holen (make native-prebuilt) und loco-server-android-arm64 bereitstellen (make android in ../bigfred oder bigfredNativeTag setzen). + Native Binärdateien fehlen. make native-prebuilt ausführen (loco-server aus GHCR + Valkey/supervisord-Releases) oder loco lokal in ../bigfred bauen. Einstellungen diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 16f703f..afbd785 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -154,7 +154,7 @@ Lokalny serwer BigFred BigFred na telefonie Uproszczona centralka jest uruchomiona - Brak binarek natywnych. Pobierz release Valkey/supervisord (make native-prebuilt) i dostarcz loco-server-android-arm64 (make android w ../bigfred lub ustaw bigfredNativeTag). + Brak binarek natywnych. Uruchom make native-prebuilt (loco-server z GHCR + release Valkey/supervisord) lub zbuduj loco lokalnie w ../bigfred. Ustawienia diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a6f18a5..1d28a0a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -154,7 +154,7 @@ Local BigFred server BigFred on phone Simplified command station is running - Native binaries missing. Fetch Valkey/supervisord releases (make native-prebuilt) and provide loco-server-android-arm64 (make android in ../bigfred or set bigfredNativeTag). + Native binaries missing. Run make native-prebuilt (GHCR loco-server + Valkey/supervisord releases) or build loco locally in ../bigfred. Settings diff --git a/gradle.properties b/gradle.properties index 78dbb2a..ba84312 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,12 +3,11 @@ android.useAndroidX=true kotlin.code.style=official android.nonTransitiveRClass=true -# Native loco-server for "BigFred on phone". +# Native loco-server for "BigFred on phone" (GHCR OCI via ORAS). # Prefer local ../bigfred/bin/loco-server-android-arm64, then native-prebuilt/, -# then latest GitHub release asset loco-server-android-arm64 from this repo. -# Optional pin: bigfredNativeTag=v1.2.3 -bigfredNativeRepo=dcc-bigfred/bigfred -bigfredNativeTag= +# then pull from GHCR. Override tag: bigfredOciTag=branch-feat-bigfred-on-phone +bigfredOciImage=ghcr.io/dcc-bigfred/loco-server-android-arm64 +bigfredOciTag=main # Valkey / supervisord Android prebuilts (latest GitHub release of each deps repo). depsAndroidValkeyRepo=dcc-bigfred/deps-android-valkey diff --git a/scripts/fetch-ghcr-oras.sh b/scripts/fetch-ghcr-oras.sh new file mode 100755 index 0000000..289b8d2 --- /dev/null +++ b/scripts/fetch-ghcr-oras.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# Pull loco-server-android-arm64 from GHCR (ORAS) and write libloco-server.so. +# Usage: fetch-ghcr-oras.sh [fallback-tag ...] +# +# Auth (optional; needed for private packages): +# GITHUB_TOKEN / GH_TOKEN / BIGFRED_NATIVE_TOKEN +set -euo pipefail + +IMAGE="${1:?usage: $0 [fallback-tag ...]}" +TAG="${2:?}" +OUT="${3:?}" +shift 3 +FALLBACK_TAGS=("$@") + +TOKEN="${BIGFRED_NATIVE_TOKEN:-${GH_TOKEN:-${GITHUB_TOKEN:-}}}" +if [[ -n "${TOKEN}" ]]; then + USER="${GITHUB_ACTOR:-oauth2}" + echo "${TOKEN}" | oras login ghcr.io -u "${USER}" --password-stdin >/dev/null +fi + +tmpdir="$(mktemp -d)" +cleanup() { rm -rf "${tmpdir}"; } +trap cleanup EXIT + +pull_tag() { + local t="$1" + rm -rf "${tmpdir:?}"/* + mkdir -p "${tmpdir}" + echo "Pulling ${IMAGE}:${t}…" + oras pull "${IMAGE}:${t}" -o "${tmpdir}" +} + +if ! pull_tag "${TAG}"; then + pulled=0 + for fb in "${FALLBACK_TAGS[@]}"; do + if pull_tag "${fb}"; then + pulled=1 + break + fi + done + if [[ "${pulled}" -eq 0 ]]; then + echo "error: could not pull ${IMAGE}:${TAG} (tried fallbacks: ${FALLBACK_TAGS[*]:-none})" >&2 + exit 1 + fi +fi + +src="${tmpdir}/loco-server-android-arm64" +if [[ ! -f "${src}" ]]; then + mapfile -t files < <(find "${tmpdir}" -type f ! -name 'manifest.json' ! -name 'config.json') + if [[ ${#files[@]} -eq 1 ]]; then + src="${files[0]}" + else + echo "error: expected loco-server-android-arm64 in OCI artifact, found:" >&2 + find "${tmpdir}" -type f >&2 + exit 1 + fi +fi + +mkdir -p "$(dirname "${OUT}")" +cp -f "${src}" "${OUT}" +chmod 755 "${OUT}" +echo "Wrote ${OUT} ($(wc -c < "${OUT}") bytes) from ${IMAGE}:${TAG}" From 1e50f9e84a37a69f0e5d48a3d0e7bd3b8256034d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20K=C4=99ska?= <372403+keskad@users.noreply.github.com> Date: Wed, 29 Jul 2026 10:47:33 +0200 Subject: [PATCH 05/17] fix(ci): always pull loco-server OCI tag main (no branch tags) GHCR publishes only :main from master; optional vars.BIGFRED_OCI_TAG for release pins (e.g. v1.2.3). Co-authored-by: Cursor --- .github/workflows/ci.yml | 12 ++---------- gradle.properties | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc043c7..0e87e85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,16 +86,8 @@ jobs: BIGFRED_OCI_TAG: ${{ vars.BIGFRED_OCI_TAG }} run: | set -euo pipefail - if [ -n "${BIGFRED_OCI_TAG:-}" ]; then - TAG="${BIGFRED_OCI_TAG}" - elif [ -n "${GITHUB_HEAD_REF:-}" ]; then - TAG="branch-$(echo "${GITHUB_HEAD_REF}" | tr '/_' '-' | tr '[:upper:]' '[:lower:]')" - elif [ "${GITHUB_REF_NAME}" = "master" ] || [ "${GITHUB_REF_NAME}" = "main" ]; then - TAG="main" - else - TAG="branch-$(echo "${GITHUB_REF_NAME}" | tr '/_' '-' | tr '[:upper:]' '[:lower:]')" - fi - echo "Using loco-server OCI tag: ${TAG} (fallback: main)" + TAG="${BIGFRED_OCI_TAG:-main}" + echo "Using loco-server OCI tag: ${TAG}" make native-prebuilt BIGFRED_OCI_TAG="${TAG}" ls -lh native-prebuilt/arm64-v8a/ diff --git a/gradle.properties b/gradle.properties index ba84312..56b77c9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ android.nonTransitiveRClass=true # Native loco-server for "BigFred on phone" (GHCR OCI via ORAS). # Prefer local ../bigfred/bin/loco-server-android-arm64, then native-prebuilt/, -# then pull from GHCR. Override tag: bigfredOciTag=branch-feat-bigfred-on-phone +# then pull from GHCR (:main or release tag via bigfredOciTag). bigfredOciImage=ghcr.io/dcc-bigfred/loco-server-android-arm64 bigfredOciTag=main From 3024ee023a0ec73b3eba9160f32781673acae656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20K=C4=99ska?= <372403+keskad@users.noreply.github.com> Date: Wed, 29 Jul 2026 12:46:59 +0200 Subject: [PATCH 06/17] fix(android): run supervisord from nativeLibraryDir, not code_cache code_cache is noexec on modern Android; pass --supervisord-bin / --supervisorctl-bin pointing at extracted lib*.so instead of copying bare names into codeCacheDir/bin. Co-authored-by: Cursor --- .../android/server/LocoServerService.kt | 32 ++++++-------- .../android/server/SupervisorPathHelper.kt | 44 ------------------- 2 files changed, 14 insertions(+), 62 deletions(-) delete mode 100644 app/src/main/java/com/dccbigfred/android/server/SupervisorPathHelper.kt diff --git a/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt b/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt index 7dd82c8..e46282e 100644 --- a/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt +++ b/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt @@ -28,7 +28,7 @@ import kotlinx.coroutines.runBlocking /** * Foreground service hosting Valkey + loco-server for "BigFred on phone". - * Stage 1 uses `--no-supervisor`; stage 2 prepares PATH for supervisord binaries. + * Stage 1 uses `--no-supervisor`; stage 2 passes absolute jniLibs paths for supervisord. */ class LocoServerService : Service() { @@ -109,9 +109,9 @@ class LocoServerService : Service() { val locoBin = NativeBinaries.require(this, NativeBinaries.LOCO_SERVER) val valkeyBin = NativeBinaries.require(this, NativeBinaries.VALKEY) - val supervised = SupervisorPathHelper.installSupervisorBinaries(this) != null && - NativeBinaries.file(this, NativeBinaries.SUPERVISORD).isFile && - NativeBinaries.file(this, NativeBinaries.SUPERVISORCTL).isFile + val supervisordBin = NativeBinaries.file(this, NativeBinaries.SUPERVISORD) + val supervisorctlBin = NativeBinaries.file(this, NativeBinaries.SUPERVISORCTL) + val supervised = supervisordBin.isFile && supervisorctlBin.isFile val prefs = (application as BigFredApplication).serverPreferences val jwt = runBlocking { prefs.getOrCreateLocalJwtSecret() } @@ -119,10 +119,10 @@ class LocoServerService : Service() { val env = HashMap(System.getenv()) env["BIGFRED_DATA_DIR"] = paths.dataDir.absolutePath env["BIGFRED_JWT_SECRET"] = jwt - env["PATH"] = SupervisorPathHelper.prependPath( - env["PATH"], - supervisorBinDir().absolutePath, - ) + if (supervised) { + // Shim resolves sibling libsupervisord.so; also set env for clarity. + env["SUPERVISORD_BIN"] = supervisordBin.absolutePath + } if (!supervised) { // Stage 1: FGS owns Valkey; loco-server runs without supervisord. @@ -140,7 +140,12 @@ class LocoServerService : Service() { ) if (supervised) { // Stage 2: loco-server owns supervisord → Valkey + dcc-bus. - Log.i(TAG, "starting supervised local mode (supervisord on PATH)") + // Execute from nativeLibraryDir (executable); never copy to code_cache (noexec). + locoArgs += listOf( + "--supervisord-bin", supervisordBin.absolutePath, + "--supervisorctl-bin", supervisorctlBin.absolutePath, + ) + Log.i(TAG, "starting supervised local mode (jniLibs supervisord)") } else { locoArgs += listOf("--redis-external", "--no-supervisor") Log.i(TAG, "starting unmanaged local mode (--no-supervisor)") @@ -260,15 +265,6 @@ class LocoServerService : Service() { } } - /** Copy supervisord/ctl into codeCacheDir/bin so LookPath finds bare names. */ - // Kept for tests / callers; delegates to SupervisorPathHelper. - fun installSupervisorPathHelpers() { - SupervisorPathHelper.installSupervisorBinaries(this) - } - - private fun supervisorBinDir(): File = - File(codeCacheDir, "bin") - /** java.lang.Process.pid() is only in the public Android SDK from API 31+. */ private fun processPid(process: Process): Long { return try { diff --git a/app/src/main/java/com/dccbigfred/android/server/SupervisorPathHelper.kt b/app/src/main/java/com/dccbigfred/android/server/SupervisorPathHelper.kt deleted file mode 100644 index dace936..0000000 --- a/app/src/main/java/com/dccbigfred/android/server/SupervisorPathHelper.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.dccbigfred.android.server - -import android.content.Context -import java.io.File - -/** - * Stage 2 scaffolding: expose supervisord/supervisorctl on PATH under [Context.getCodeCacheDir]. - * Stage 1 still runs loco-server with `--no-supervisor`. - */ -object SupervisorPathHelper { - private const val BIN_SUBDIR = "bin" - - fun installSupervisorBinaries(context: Context): String? { - val binDir = File(context.codeCacheDir, BIN_SUBDIR) - binDir.mkdirs() - var installed = false - listOf( - NativeBinaries.SUPERVISORD to "supervisord", - NativeBinaries.SUPERVISORCTL to "supervisorctl", - ).forEach { (libName, destName) -> - val src = NativeBinaries.file(context, libName) - if (!src.isFile) return@forEach - copyExecutable(src, File(binDir, destName)) - installed = true - } - return if (installed) binDir.absolutePath else null - } - - fun prependPath(existingPath: String?, binDir: String): String { - val trimmed = binDir.trimEnd('/') - return when { - existingPath.isNullOrBlank() -> trimmed - existingPath.split(':').any { it == trimmed } -> existingPath - else -> "$trimmed:$existingPath" - } - } - - private fun copyExecutable(source: File, dest: File) { - source.inputStream().use { input -> - dest.outputStream().use { output -> input.copyTo(output) } - } - dest.setExecutable(true, false) - } -} From 91fe6b6aafed2f4b482cf92e9e419cbf5e26d3e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20K=C4=99ska?= <372403+keskad@users.noreply.github.com> Date: Wed, 29 Jul 2026 21:54:08 +0200 Subject: [PATCH 07/17] feat(android): set BIGFRED_LAN_PREFIX from NetworkInterface Avoids Go netlink denial on phone; loco-server forwards the env as dcc-bus scan --lan-prefix. Co-authored-by: Cursor --- .../dccbigfred/android/server/LanPrefix.kt | 48 +++++++++++++++++++ .../android/server/LocoServerService.kt | 4 ++ 2 files changed, 52 insertions(+) create mode 100644 app/src/main/java/com/dccbigfred/android/server/LanPrefix.kt diff --git a/app/src/main/java/com/dccbigfred/android/server/LanPrefix.kt b/app/src/main/java/com/dccbigfred/android/server/LanPrefix.kt new file mode 100644 index 0000000..05a4bb4 --- /dev/null +++ b/app/src/main/java/com/dccbigfred/android/server/LanPrefix.kt @@ -0,0 +1,48 @@ +package com.dccbigfred.android.server + +import android.util.Log +import java.net.Inet4Address +import java.net.NetworkInterface + +/** + * Resolves a LAN scan prefix (e.g. "192.168.0") via Java NetworkInterface. + * Used because Go's net.InterfaceAddrs uses netlink, which Android denies to apps. + */ +object LanPrefix { + private const val TAG = "LanPrefix" + + /** + * Returns a.b.c for the first non-loopback, non-link-local IPv4, preferring /24. + */ + fun resolve(): String? { + return try { + val ifaces = NetworkInterface.getNetworkInterfaces() ?: return null + var fallback: String? = null + while (ifaces.hasMoreElements()) { + val nif = ifaces.nextElement() + if (!nif.isUp || nif.isLoopback) continue + val addrs = nif.inetAddresses + while (addrs.hasMoreElements()) { + val addr = addrs.nextElement() + if (addr !is Inet4Address || addr.isLoopbackAddress || addr.isLinkLocalAddress) { + continue + } + val bytes = addr.address ?: continue + if (bytes.size != 4) continue + val prefix = "${bytes[0].toUByte()}.${bytes[1].toUByte()}.${bytes[2].toUByte()}" + // NetworkInterface does not expose prefix length portably on all APIs; + // prefer site-local (RFC1918) as the usual LAN case. + if (addr.isSiteLocalAddress) { + Log.i(TAG, "LAN prefix $prefix from ${nif.name} (${addr.hostAddress})") + return prefix + } + if (fallback == null) fallback = prefix + } + } + fallback?.also { Log.i(TAG, "LAN prefix $it (non-site-local fallback)") } + } catch (e: Exception) { + Log.w(TAG, "failed to resolve LAN prefix", e) + null + } + } +} diff --git a/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt b/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt index e46282e..7a57cfb 100644 --- a/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt +++ b/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt @@ -119,6 +119,10 @@ class LocoServerService : Service() { val env = HashMap(System.getenv()) env["BIGFRED_DATA_DIR"] = paths.dataDir.absolutePath env["BIGFRED_JWT_SECRET"] = jwt + LanPrefix.resolve()?.let { prefix -> + env["BIGFRED_LAN_PREFIX"] = prefix + Log.i(TAG, "BIGFRED_LAN_PREFIX=$prefix (for dcc-bus scan --lan-prefix)") + } if (supervised) { // Shim resolves sibling libsupervisord.so; also set env for clarity. env["SUPERVISORD_BIN"] = supervisordBin.absolutePath From cb0c57235a693a184bd4235de701f7a07080462e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20K=C4=99ska?= <372403+keskad@users.noreply.github.com> Date: Wed, 29 Jul 2026 23:10:57 +0200 Subject: [PATCH 08/17] feat(android): redesign on-phone BigFred discovery and intro Hide connection status while local hub is active. Move on-phone start into an expandable section above Manual, with a dedicated intro screen and confirm/back actions instead of a dialog. Co-authored-by: Cursor --- .../android/ui/discovery/DiscoveryScreen.kt | 103 +++++++++--------- .../ui/localserver/LocalServerIntroScreen.kt | 81 ++++++++++++++ .../android/ui/navigation/BigFredApp.kt | 19 +++- .../android/ui/navigation/Routes.kt | 1 + app/src/main/res/values-de/strings.xml | 10 +- app/src/main/res/values-pl/strings.xml | 10 +- app/src/main/res/values/strings.xml | 10 +- 7 files changed, 168 insertions(+), 66 deletions(-) create mode 100644 app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerIntroScreen.kt diff --git a/app/src/main/java/com/dccbigfred/android/ui/discovery/DiscoveryScreen.kt b/app/src/main/java/com/dccbigfred/android/ui/discovery/DiscoveryScreen.kt index a5d56a2..bb8f3d0 100644 --- a/app/src/main/java/com/dccbigfred/android/ui/discovery/DiscoveryScreen.kt +++ b/app/src/main/java/com/dccbigfred/android/ui/discovery/DiscoveryScreen.kt @@ -22,6 +22,7 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ExpandLess import androidx.compose.material.icons.filled.ExpandMore +import androidx.compose.material.icons.filled.PhoneAndroid import androidx.compose.material.icons.filled.Refresh import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator @@ -34,10 +35,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar -import androidx.compose.material.icons.filled.PhoneAndroid import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue @@ -66,9 +64,9 @@ import kotlinx.coroutines.launch @Composable fun DiscoveryScreen( onServerSelected: (String) -> Unit, - onStartLocalServer: () -> Unit = {}, localServerRunning: Boolean = false, onOpenLocalStatus: () -> Unit = {}, + onOpenLocalIntro: () -> Unit = {}, ) { val context = LocalContext.current val discovery = remember { ServerDiscovery(context) } @@ -81,7 +79,7 @@ fun DiscoveryScreen( var manualError by remember { mutableStateOf(null) } var manualBusy by remember { mutableStateOf(false) } var manualExpanded by remember { mutableStateOf(false) } - var showLocalWarning by remember { mutableStateOf(false) } + var phoneExpanded by remember { mutableStateOf(false) } val errorHostRequired = stringResource(R.string.discovery_error_host_required) val errorUnreachable = stringResource(R.string.discovery_error_unreachable) @@ -159,30 +157,6 @@ fun DiscoveryScreen( ) Spacer(modifier = Modifier.height(12.dp)) - if (localServerRunning) { - ListItem( - headlineContent = { - Text(stringResource(R.string.discovery_on_phone_running)) - }, - leadingContent = { - Icon(Icons.Default.PhoneAndroid, contentDescription = null) - }, - modifier = Modifier.clickable(onClick = onOpenLocalStatus), - ) - HorizontalDivider() - Spacer(modifier = Modifier.height(8.dp)) - } else { - Button( - onClick = { showLocalWarning = true }, - modifier = Modifier.fillMaxWidth(), - ) { - Icon(Icons.Default.PhoneAndroid, contentDescription = null) - Spacer(modifier = Modifier.width(8.dp)) - Text(stringResource(R.string.discovery_on_phone_title)) - } - Spacer(modifier = Modifier.height(12.dp)) - } - if (scanning) { Row(verticalAlignment = Alignment.CenterVertically) { CircularProgressIndicator(modifier = Modifier.size(20.dp), strokeWidth = 2.dp) @@ -212,6 +186,54 @@ fun DiscoveryScreen( } } + // Simplified on-phone hub — same expandable style as Manual, above it. + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { phoneExpanded = !phoneExpanded } + .padding(vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(R.string.discovery_phone_section), + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.weight(1f), + ) + Icon( + imageVector = if (phoneExpanded) { + Icons.Default.ExpandLess + } else { + Icons.Default.ExpandMore + }, + contentDescription = null, + ) + } + AnimatedVisibility(visible = phoneExpanded) { + Column { + if (localServerRunning) { + ListItem( + headlineContent = { + Text(stringResource(R.string.discovery_on_phone_running)) + }, + leadingContent = { + Icon(Icons.Default.PhoneAndroid, contentDescription = null) + }, + modifier = Modifier.clickable(onClick = onOpenLocalStatus), + ) + } else { + Button( + onClick = onOpenLocalIntro, + modifier = Modifier.fillMaxWidth(), + ) { + Icon(Icons.Default.PhoneAndroid, contentDescription = null) + Spacer(modifier = Modifier.width(8.dp)) + Text(stringResource(R.string.discovery_on_phone_builtin)) + } + } + Spacer(modifier = Modifier.height(8.dp)) + } + } + Row( modifier = Modifier .fillMaxWidth() @@ -287,29 +309,6 @@ fun DiscoveryScreen( } } } - - if (showLocalWarning) { - AlertDialog( - onDismissRequest = { showLocalWarning = false }, - title = { Text(stringResource(R.string.discovery_on_phone_warning_title)) }, - text = { Text(stringResource(R.string.discovery_on_phone_warning_body)) }, - confirmButton = { - TextButton( - onClick = { - showLocalWarning = false - onStartLocalServer() - }, - ) { - Text(stringResource(R.string.discovery_on_phone_confirm)) - } - }, - dismissButton = { - TextButton(onClick = { showLocalWarning = false }) { - Text(stringResource(R.string.discovery_on_phone_cancel)) - } - }, - ) - } } @Composable diff --git a/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerIntroScreen.kt b/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerIntroScreen.kt new file mode 100644 index 0000000..3febb2d --- /dev/null +++ b/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerIntroScreen.kt @@ -0,0 +1,81 @@ +package com.dccbigfred.android.ui.localserver + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.dccbigfred.android.R +import com.dccbigfred.android.ui.components.topAppBarEdgePadding + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun LocalServerIntroScreen( + onBack: () -> Unit, + onConfirmStart: () -> Unit, +) { + Scaffold( + topBar = { + TopAppBar( + modifier = Modifier.topAppBarEdgePadding(), + title = { Text(stringResource(R.string.local_server_intro_title)) }, + navigationIcon = { + IconButton(onClick = onBack) { + Icon( + Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(R.string.back), + ) + } + }, + ) + }, + ) { padding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()), + ) { + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = stringResource(R.string.local_server_intro_body), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface, + ) + Spacer(modifier = Modifier.height(24.dp)) + Button( + onClick = onConfirmStart, + modifier = Modifier.fillMaxWidth(), + ) { + Text(stringResource(R.string.local_server_intro_confirm)) + } + Spacer(modifier = Modifier.height(8.dp)) + OutlinedButton( + onClick = onBack, + modifier = Modifier.fillMaxWidth(), + ) { + Text(stringResource(R.string.local_server_intro_back)) + } + Spacer(modifier = Modifier.height(16.dp)) + } + } +} diff --git a/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt b/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt index fcb27b1..d03c455 100644 --- a/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt +++ b/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt @@ -78,6 +78,7 @@ import com.dccbigfred.android.server.LocoServerService import com.dccbigfred.android.ui.about.AboutScreen import com.dccbigfred.android.ui.connection.ConnectionStatusScreen import com.dccbigfred.android.ui.discovery.DiscoveryScreen +import com.dccbigfred.android.ui.localserver.LocalServerIntroScreen import com.dccbigfred.android.ui.localserver.LocalServerStatusScreen import com.dccbigfred.android.ui.models.ModelsCatalogScreen import com.dccbigfred.android.ui.myvehicles.MyVehiclesScreen @@ -244,6 +245,7 @@ fun BigFredApp() { // Status screen shows Starting / Failed / Running while boot runs. navController.navigate(Routes.LOCAL_SERVER) { launchSingleTop = true + popUpTo(Routes.LOCAL_SERVER_INTRO) { inclusive = true } } LocoServerService.start(context) val deadline = System.currentTimeMillis() + 60_000 @@ -377,7 +379,10 @@ fun BigFredApp() { } }, ) - if (selectedServerUrl != null) { + if (selectedServerUrl != null && + localServerState !is LocalServerState.Running && + !LocoServerService.isLocalUrl(selectedServerUrl) + ) { NavigationDrawerItem( label = { Text(stringResource(R.string.menu_connection_status)) }, selected = currentRoute == Routes.CONNECTION, @@ -510,13 +515,23 @@ fun BigFredApp() { composable(Routes.DISCOVERY) { DiscoveryScreen( onServerSelected = { url -> goToWebView(url) }, - onStartLocalServer = { startLocalServer() }, localServerRunning = localServerState is LocalServerState.Running, onOpenLocalStatus = { navController.navigate(Routes.LOCAL_SERVER) { launchSingleTop = true } }, + onOpenLocalIntro = { + navController.navigate(Routes.LOCAL_SERVER_INTRO) { + launchSingleTop = true + } + }, + ) + } + composable(Routes.LOCAL_SERVER_INTRO) { + LocalServerIntroScreen( + onBack = { navController.popBackStack() }, + onConfirmStart = { startLocalServer() }, ) } composable(Routes.LOCAL_SERVER) { diff --git a/app/src/main/java/com/dccbigfred/android/ui/navigation/Routes.kt b/app/src/main/java/com/dccbigfred/android/ui/navigation/Routes.kt index 9d0d0db..0d74af9 100644 --- a/app/src/main/java/com/dccbigfred/android/ui/navigation/Routes.kt +++ b/app/src/main/java/com/dccbigfred/android/ui/navigation/Routes.kt @@ -10,4 +10,5 @@ object Routes { const val MY_VEHICLES = "my_vehicles" const val ABOUT = "about" const val LOCAL_SERVER = "local_server" + const val LOCAL_SERVER_INTRO = "local_server_intro" } diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index cf4dcd5..ddfd4de 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -133,14 +133,16 @@ manuell + Vereinfachter Server auf dem Telefon + Eingebautes BigFred auf diesem Telefon BigFred auf dem Telefon BigFred auf dem Telefon (aktiv) - Vereinfachter Modus - Achtung: BigFred auf dem Telefon ist nur eine VEREINFACHTE Anwendung, mit der sich kein zweites Telefon, keine ROCO WlanMaus, kein WiFred und kein LongFred verbinden kann. Verwenden Sie BigFred im vereinfachten Modus nicht gleichzeitig auf mehreren Telefonen mit derselben Zentrale. Für mehrere Personen/Telefone/Handregler starten Sie BigFred auf einem Raspberry Pi 4/5+ - Weiter - Abbrechen BigFred wird auf diesem Telefon gestartet… Lokales BigFred konnte nicht gestartet werden: %1$s + Eingebautes BigFred auf diesem Telefon + BigFred ist als zentraler Hub gedacht — ein physisches Gerät neben der Zentrale, an das sich Telefone, WlanMäuse, WiFreds und LongFreds anbinden. Durch die Zentralisierung können mehrere Nutzer gemeinsam fahren; Funktionen wie Totmann und Notbremsung bei Reichweitenverlust sind möglich.\n\nWenn Sie die Hub-Software als BigFred auf dem Telefon starten, erhalten Sie eine funktions- und sicherheitsreduzierte Variante. Sie kann keinen Zug notbremsen, wenn der Akku leer ist, Sie können kein zweites Telefon anbinden und keine WlanMaus, keinen WiFred und keinen LongFred anschließen. Betrachten Sie das als Demo vor dem Kauf von Hardware für BigFred. + Zurück + Verstanden, trotzdem starten BigFred auf dem Telefon: aktiv BigFred auf dem Telefon Server stoppen diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index afbd785..7e0bba5 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -133,14 +133,16 @@ ręczny + Uproszczony serwer na telefonie + Wbudowany BigFred na tym telefonie BigFred na telefonie BigFred na telefonie (działa) - Tryb uproszczony - Uwaga: BigFred uruchomiony na telefonie jest jedynie UPROSZCZONĄ aplikacją, do której nie połączy się drugi telefon, ROCO WlanMaus, WiFred ani LongFred. Nie używaj BigFreda w trybie uproszczonym na wielu telefonach jednocześnie z tą samą centralką. Aby używać BigFred przez wiele osób/telefonów/pilotów uruchom go na Raspberry Pi 4/5+ - Kontynuuj - Anuluj Uruchamianie BigFred na tym telefonie… Nie udało się uruchomić lokalnego BigFred: %1$s + Wbudowany BigFred na tym telefonie + BigFred to z założenia centralny hub, fizyczne urządzenie leżące obok centralki, do którego podłączają się telefony, wlanmausy, wifredy, longfredy. Dzięki centralizacji hub umożliwia sterowanie wielu użytkownikom oraz posiada takie funkcjonalności jak np. czuwak i awaryjne hamowanie przy utracie zasięgu.\n\nUruchamiając oprogramowanie huba — BigFred na telefonie — otrzymasz okrojoną z funkcji i zabezpieczeń wersję. Nie będzie ona w stanie awaryjnie zahamować składu, gdy rozładuje Ci się telefon, nie podłączysz do niej drugiego telefonu, nie podłączysz Wlanmausa, WiFreda ani LongFreda. Potraktuj to jako wersję pokazową przed zakupem urządzenia do uruchomienia BigFreda. + Cofnij + Rozumiem, uruchom pomimo tego BigFred na telefonie: działa BigFred na telefonie Zatrzymaj serwer diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1d28a0a..99c80f1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -133,14 +133,16 @@ manual + Simplified server on phone + Built-in BigFred on this phone BigFred on phone BigFred on phone (running) - Simplified mode - Warning: BigFred running on a phone is only a SIMPLIFIED application that a second phone, ROCO WlanMaus, WiFred or LongFred cannot connect to. Do not use BigFred in simplified mode on multiple phones at the same time with the same command station. To use BigFred with multiple people/phones/controllers, run it on a Raspberry Pi 4/5+ - Continue - Cancel Starting BigFred on this phone… Failed to start local BigFred: %1$s + Built-in BigFred on this phone + BigFred is designed as a central hub — a physical device next to the command station that phones, WlanMaus units, WiFreds and LongFreds connect to. Centralisation lets multiple users drive together and enables features such as dead-man protection and emergency braking when radio range is lost.\n\nRunning hub software as BigFred on a phone gives you a cut-down build with fewer features and safeguards. It cannot emergency-brake a train if your phone battery dies, you cannot connect a second phone, and you cannot attach a WlanMaus, WiFred or LongFred. Treat it as a demo before buying hardware to run BigFred properly. + Back + I understand — start anyway BigFred on phone: running BigFred on phone Stop server From fe936181fc76fe2bc8252729d8c6aa6d58942870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20K=C4=99ska?= <372403+keskad@users.noreply.github.com> Date: Wed, 29 Jul 2026 23:12:28 +0200 Subject: [PATCH 09/17] feat(android): show hub network diagram on local BigFred intro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a resized YaMoRC↔router↔BigFred diagram at the top of the built-in BigFred intro screen. Co-authored-by: Cursor --- .../ui/localserver/LocalServerIntroScreen.kt | 14 ++++++++++++++ .../res/drawable/yd7001_bigfred_network.png | Bin 0 -> 254020 bytes app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-pl/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 5 files changed, 17 insertions(+) create mode 100644 app/src/main/res/drawable/yd7001_bigfred_network.png diff --git a/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerIntroScreen.kt b/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerIntroScreen.kt index 3febb2d..8037708 100644 --- a/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerIntroScreen.kt +++ b/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerIntroScreen.kt @@ -1,5 +1,6 @@ package com.dccbigfred.android.ui.localserver +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -7,6 +8,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack @@ -21,6 +23,9 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.dccbigfred.android.R @@ -56,6 +61,15 @@ fun LocalServerIntroScreen( .verticalScroll(rememberScrollState()), ) { Spacer(modifier = Modifier.height(8.dp)) + Image( + painter = painterResource(R.drawable.yd7001_bigfred_network), + contentDescription = stringResource(R.string.local_server_intro_diagram_cd), + contentScale = ContentScale.FillWidth, + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)), + ) + Spacer(modifier = Modifier.height(16.dp)) Text( text = stringResource(R.string.local_server_intro_body), style = MaterialTheme.typography.bodyLarge, diff --git a/app/src/main/res/drawable/yd7001_bigfred_network.png b/app/src/main/res/drawable/yd7001_bigfred_network.png new file mode 100644 index 0000000000000000000000000000000000000000..34126929f6b73791941a5bf48e6e419927c4f9d7 GIT binary patch literal 254020 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

RC;C;oob{!j^ESi=o!UORB&CUGS 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%0A(L+eQJ!q&O3R8o_;p@14GR5{xv^eDMsV% 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 Lu6{1-oD!MBigFred wird auf diesem Telefon gestartet… Lokales BigFred konnte nicht gestartet werden: %1$s Eingebautes BigFred auf diesem Telefon + Typisches BigFred-Hub-Netzwerk: Zentrale, Router und BigFred BigFred ist als zentraler Hub gedacht — ein physisches Gerät neben der Zentrale, an das sich Telefone, WlanMäuse, WiFreds und LongFreds anbinden. Durch die Zentralisierung können mehrere Nutzer gemeinsam fahren; Funktionen wie Totmann und Notbremsung bei Reichweitenverlust sind möglich.\n\nWenn Sie die Hub-Software als BigFred auf dem Telefon starten, erhalten Sie eine funktions- und sicherheitsreduzierte Variante. Sie kann keinen Zug notbremsen, wenn der Akku leer ist, Sie können kein zweites Telefon anbinden und keine WlanMaus, keinen WiFred und keinen LongFred anschließen. Betrachten Sie das als Demo vor dem Kauf von Hardware für BigFred. Zurück Verstanden, trotzdem starten diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 7e0bba5..f7551dd 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -140,6 +140,7 @@ Uruchamianie BigFred na tym telefonie… Nie udało się uruchomić lokalnego BigFred: %1$s Wbudowany BigFred na tym telefonie + Typowa sieć huba BigFred: centralka, router i BigFred BigFred to z założenia centralny hub, fizyczne urządzenie leżące obok centralki, do którego podłączają się telefony, wlanmausy, wifredy, longfredy. Dzięki centralizacji hub umożliwia sterowanie wielu użytkownikom oraz posiada takie funkcjonalności jak np. czuwak i awaryjne hamowanie przy utracie zasięgu.\n\nUruchamiając oprogramowanie huba — BigFred na telefonie — otrzymasz okrojoną z funkcji i zabezpieczeń wersję. Nie będzie ona w stanie awaryjnie zahamować składu, gdy rozładuje Ci się telefon, nie podłączysz do niej drugiego telefonu, nie podłączysz Wlanmausa, WiFreda ani LongFreda. Potraktuj to jako wersję pokazową przed zakupem urządzenia do uruchomienia BigFreda. Cofnij Rozumiem, uruchom pomimo tego diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 99c80f1..078f903 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -140,6 +140,7 @@ Starting BigFred on this phone… Failed to start local BigFred: %1$s Built-in BigFred on this phone + Typical BigFred hub network: command station, router and BigFred BigFred is designed as a central hub — a physical device next to the command station that phones, WlanMaus units, WiFreds and LongFreds connect to. Centralisation lets multiple users drive together and enables features such as dead-man protection and emergency braking when radio range is lost.\n\nRunning hub software as BigFred on a phone gives you a cut-down build with fewer features and safeguards. It cannot emergency-brake a train if your phone battery dies, you cannot connect a second phone, and you cannot attach a WlanMaus, WiFred or LongFred. Treat it as a demo before buying hardware to run BigFred properly. Back I understand — start anyway From c6af38442754437b45c2eba8d0938f7e5bafab34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20K=C4=99ska?= <372403+keskad@users.noreply.github.com> Date: Wed, 29 Jul 2026 23:18:15 +0200 Subject: [PATCH 10/17] feat(android): improve local server notification and status UX Open WebView when the foreground notification is tapped, update notification copy, and collapse server logs by default on the status screen. Co-authored-by: Cursor --- app/src/main/AndroidManifest.xml | 1 + .../com/dccbigfred/android/MainActivity.kt | 24 +++++++++- .../android/server/LocoServerService.kt | 7 ++- .../ui/localserver/LocalServerStatusScreen.kt | 45 +++++++++++++++---- .../android/ui/navigation/BigFredApp.kt | 16 ++++++- app/src/main/res/values-de/strings.xml | 3 +- app/src/main/res/values-pl/strings.xml | 3 +- app/src/main/res/values/strings.xml | 3 +- 8 files changed, 87 insertions(+), 15 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index af496c0..d1369a2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -28,6 +28,7 @@ diff --git a/app/src/main/java/com/dccbigfred/android/MainActivity.kt b/app/src/main/java/com/dccbigfred/android/MainActivity.kt index ace5369..05ef74e 100644 --- a/app/src/main/java/com/dccbigfred/android/MainActivity.kt +++ b/app/src/main/java/com/dccbigfred/android/MainActivity.kt @@ -1,5 +1,6 @@ package com.dccbigfred.android +import android.content.Intent import android.os.Bundle import android.view.KeyEvent import android.view.WindowManager @@ -9,8 +10,11 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat +import com.dccbigfred.android.server.LocoServerService import com.dccbigfred.android.ui.navigation.BigFredApp import com.dccbigfred.android.ui.theme.BigFredTheme +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.receiveAsFlow class MainActivity : AppCompatActivity() { /** @@ -19,19 +23,37 @@ class MainActivity : AppCompatActivity() { */ var volumeKeyInterceptor: ((KeyEvent) -> Boolean)? = null + private val openLocalWebViewChannel = Channel(Channel.BUFFERED) + val openLocalWebViewRequests = openLocalWebViewChannel.receiveAsFlow() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() WindowCompat.setDecorFitsSystemWindows(window, false) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) hideSystemBars() + handleNotificationIntent(intent) setContent { BigFredTheme { - BigFredApp() + BigFredApp(openLocalWebViewRequests = openLocalWebViewRequests) } } } + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + setIntent(intent) + handleNotificationIntent(intent) + } + + private fun handleNotificationIntent(intent: Intent?) { + if (intent?.getBooleanExtra(LocoServerService.EXTRA_OPEN_LOCAL_WEBVIEW, false) != true) { + return + } + intent.removeExtra(LocoServerService.EXTRA_OPEN_LOCAL_WEBVIEW) + openLocalWebViewChannel.trySend(Unit) + } + override fun dispatchKeyEvent(event: KeyEvent): Boolean { val code = event.keyCode if (code == KeyEvent.KEYCODE_VOLUME_UP || code == KeyEvent.KEYCODE_VOLUME_DOWN) { diff --git a/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt b/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt index 7a57cfb..cd12466 100644 --- a/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt +++ b/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt @@ -286,10 +286,14 @@ class LocoServerService : Service() { private fun startForegroundNotification() { ensureChannel() + val openActivityIntent = Intent(this, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP + putExtra(EXTRA_OPEN_LOCAL_WEBVIEW, true) + } val openIntent = PendingIntent.getActivity( this, 0, - Intent(this, MainActivity::class.java), + openActivityIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, ) val stopIntent = PendingIntent.getService( @@ -354,6 +358,7 @@ class LocoServerService : Service() { const val HTTP_PORT = 8080 const val REDIS_PORT = 6379 const val LOCAL_BASE_URL = "http://127.0.0.1:$HTTP_PORT" + const val EXTRA_OPEN_LOCAL_WEBVIEW = "com.dccbigfred.android.OPEN_LOCAL_WEBVIEW" const val ACTION_START = "com.dccbigfred.android.server.START" const val ACTION_STOP = "com.dccbigfred.android.server.STOP" diff --git a/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerStatusScreen.kt b/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerStatusScreen.kt index cae7166..b717f71 100644 --- a/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerStatusScreen.kt +++ b/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerStatusScreen.kt @@ -1,5 +1,7 @@ package com.dccbigfred.android.ui.localserver +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -12,9 +14,13 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ExpandLess +import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Scaffold @@ -24,6 +30,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -48,6 +55,7 @@ fun LocalServerStatusScreen( ) { val context = LocalContext.current val state by LocoServerService.state.collectAsStateWithLifecycle() + var logsExpanded by remember { mutableStateOf(false) } var logTick by remember { mutableIntStateOf(0) } LaunchedEffect(state) { if (state is LocalServerState.Starting) { @@ -144,15 +152,34 @@ fun LocalServerStatusScreen( } Spacer(modifier = Modifier.height(8.dp)) - Text( - text = stringResource(R.string.local_server_logs), - style = MaterialTheme.typography.titleMedium, - ) - Text( - text = logs.ifBlank { "—" }, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { logsExpanded = !logsExpanded } + .padding(vertical = 4.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(R.string.local_server_logs), + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.weight(1f), + ) + Icon( + imageVector = if (logsExpanded) { + Icons.Default.ExpandLess + } else { + Icons.Default.ExpandMore + }, + contentDescription = null, + ) + } + AnimatedVisibility(visible = logsExpanded) { + Text( + text = logs.ifBlank { "—" }, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } } } } diff --git a/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt b/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt index d03c455..9f47cc2 100644 --- a/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt +++ b/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt @@ -89,6 +89,8 @@ import com.dccbigfred.android.ui.webview.applyLocaleToWebView import com.dccbigfred.android.ui.webview.deliverThrottleHardwareKeys import com.dccbigfred.android.ui.webview.handleVolumeKeyEvent import com.dccbigfred.android.wifi.LowLatencyWifiLock +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.launch /** Hit target / drag strip at the physical left edge. */ @@ -99,7 +101,9 @@ private val DrawerHandleHeight = 56.dp private const val DrawerOpenDragThresholdPx = 40f @Composable -fun BigFredApp() { +fun BigFredApp( + openLocalWebViewRequests: Flow = emptyFlow(), +) { val context = LocalContext.current val app = context.applicationContext as BigFredApplication val prefs = app.serverPreferences @@ -184,6 +188,16 @@ fun BigFredApp() { } } + LaunchedEffect(Unit) { + openLocalWebViewRequests.collect { + val url = when (val s = LocoServerService.state.value) { + is LocalServerState.Running -> s.baseUrl + else -> LocoServerService.LOCAL_BASE_URL + } + goToWebView(url) + } + } + fun openBigFredApp() { scope.launch { drawerState.close() diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 7b70cb6..918496d 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -139,6 +139,7 @@ BigFred auf dem Telefon (aktiv) BigFred wird auf diesem Telefon gestartet… Lokales BigFred konnte nicht gestartet werden: %1$s + OK Eingebautes BigFred auf diesem Telefon Typisches BigFred-Hub-Netzwerk: Zentrale, Router und BigFred BigFred ist als zentraler Hub gedacht — ein physisches Gerät neben der Zentrale, an das sich Telefone, WlanMäuse, WiFreds und LongFreds anbinden. Durch die Zentralisierung können mehrere Nutzer gemeinsam fahren; Funktionen wie Totmann und Notbremsung bei Reichweitenverlust sind möglich.\n\nWenn Sie die Hub-Software als BigFred auf dem Telefon starten, erhalten Sie eine funktions- und sicherheitsreduzierte Variante. Sie kann keinen Zug notbremsen, wenn der Akku leer ist, Sie können kein zweites Telefon anbinden und keine WlanMaus, keinen WiFred und keinen LongFred anschließen. Betrachten Sie das als Demo vor dem Kauf von Hardware für BigFred. @@ -156,7 +157,7 @@ Fehler: %1$s Lokaler BigFred-Server BigFred auf dem Telefon - Vereinfachte Zentrale läuft + Tippen, um BigFred zu öffnen Native Binärdateien fehlen. make native-prebuilt ausführen (loco-server aus GHCR + Valkey/supervisord-Releases) oder loco lokal in ../bigfred bauen. diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index f7551dd..0321cc6 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -139,6 +139,7 @@ BigFred na telefonie (działa) Uruchamianie BigFred na tym telefonie… Nie udało się uruchomić lokalnego BigFred: %1$s + OK Wbudowany BigFred na tym telefonie Typowa sieć huba BigFred: centralka, router i BigFred BigFred to z założenia centralny hub, fizyczne urządzenie leżące obok centralki, do którego podłączają się telefony, wlanmausy, wifredy, longfredy. Dzięki centralizacji hub umożliwia sterowanie wielu użytkownikom oraz posiada takie funkcjonalności jak np. czuwak i awaryjne hamowanie przy utracie zasięgu.\n\nUruchamiając oprogramowanie huba — BigFred na telefonie — otrzymasz okrojoną z funkcji i zabezpieczeń wersję. Nie będzie ona w stanie awaryjnie zahamować składu, gdy rozładuje Ci się telefon, nie podłączysz do niej drugiego telefonu, nie podłączysz Wlanmausa, WiFreda ani LongFreda. Potraktuj to jako wersję pokazową przed zakupem urządzenia do uruchomienia BigFreda. @@ -156,7 +157,7 @@ Błąd: %1$s Lokalny serwer BigFred BigFred na telefonie - Uproszczona centralka jest uruchomiona + Kliknij aby przejść do BigFred Brak binarek natywnych. Uruchom make native-prebuilt (loco-server z GHCR + release Valkey/supervisord) lub zbuduj loco lokalnie w ../bigfred. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 078f903..ff19f3e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -139,6 +139,7 @@ BigFred on phone (running) Starting BigFred on this phone… Failed to start local BigFred: %1$s + OK Built-in BigFred on this phone Typical BigFred hub network: command station, router and BigFred BigFred is designed as a central hub — a physical device next to the command station that phones, WlanMaus units, WiFreds and LongFreds connect to. Centralisation lets multiple users drive together and enables features such as dead-man protection and emergency braking when radio range is lost.\n\nRunning hub software as BigFred on a phone gives you a cut-down build with fewer features and safeguards. It cannot emergency-brake a train if your phone battery dies, you cannot connect a second phone, and you cannot attach a WlanMaus, WiFred or LongFred. Treat it as a demo before buying hardware to run BigFred properly. @@ -156,7 +157,7 @@ Failed: %1$s Local BigFred server BigFred on phone - Simplified command station is running + Tap to open BigFred Native binaries missing. Run make native-prebuilt (GHCR loco-server + Valkey/supervisord releases) or build loco locally in ../bigfred. From 41b955461ed45b42d9c788197d45d23a1234b730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20K=C4=99ska?= <372403+keskad@users.noreply.github.com> Date: Wed, 29 Jul 2026 23:58:04 +0200 Subject: [PATCH 11/17] fix(android): harden local server lifecycle and restore catalog add Cancel the boot thread on stop, reject concurrent boots, route notification taps to the status screen until the server is Running, and restore the long-press "Add to my vehicles" action in the model catalog. Co-authored-by: Cursor --- .../android/server/LocoServerService.kt | 38 +++++++++++++++++-- .../android/ui/navigation/BigFredApp.kt | 23 +++++++++-- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt b/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt index cd12466..41b2072 100644 --- a/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt +++ b/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt @@ -36,6 +36,10 @@ class LocoServerService : Service() { private var valkeyProcess: Process? = null private var locoProcess: Process? = null private var watchdogThread: Thread? = null + private var bootThread: Thread? = null + /** True while a boot is in progress; gates concurrent ACTION_START deliveries. */ + private val booting = AtomicBoolean(false) + /** True once boot() reached Running; cleared on stop or boot failure. */ private val running = AtomicBoolean(false) override fun onBind(intent: Intent?): IBinder? = null @@ -48,27 +52,32 @@ class LocoServerService : Service() { return START_NOT_STICKY } ACTION_START, null -> { - if (!running.get()) { + if (booting.compareAndSet(false, true)) { _state.value = LocalServerState.Starting try { startForegroundNotification() } catch (e: Exception) { Log.e(TAG, "startForeground failed", e) + booting.set(false) _state.value = LocalServerState.Failed(e.message ?: e.toString()) stopSelf() return START_NOT_STICKY } acquireWakeLock() - thread(name = "loco-server-boot", isDaemon = true) { + bootThread = thread(name = "loco-server-boot", isDaemon = true) { try { boot() + } catch (e: InterruptedException) { + // stop requested during boot — cleanup already done by stopLocalServer + Log.i(TAG, "boot interrupted, stopping") } catch (e: Exception) { Log.e(TAG, "local server start failed", e) cleanupChildren() releaseWakeLock() - // Must set Failed after cleanup — stopLocalServer() would clear it to Stopped. _state.value = LocalServerState.Failed(e.message ?: e.toString()) stopSelf() + } finally { + booting.set(false) } } } @@ -85,6 +94,9 @@ class LocoServerService : Service() { _state.value = LocalServerState.Stopped } running.set(false) + booting.set(false) + bootThread?.interrupt() + bootThread = null watchdogThread?.interrupt() watchdogThread = null super.onDestroy() @@ -164,6 +176,12 @@ class LocoServerService : Service() { ProcessOrphanReaper.writePid(paths.locoServerPid, processPid(locoProcess!!)) waitForHttpReady(45_000) + // stop may have been requested while we were waiting for HTTP — bail + // before flipping to Running / starting the watchdog, otherwise an + // interrupted boot would resurrect the service after stop. + if (Thread.currentThread().isInterrupted || !booting.get()) { + throw InterruptedException("stopped during boot") + } running.set(true) _state.value = LocalServerState.Running(LOCAL_BASE_URL) startWatchdog(supervised) @@ -219,7 +237,15 @@ class LocoServerService : Service() { Log.w(TAG, "child died loco=$locoAlive valkeyOk=$valkeyOk — restarting") cleanupChildren() running.set(false) - boot() + if (!booting.compareAndSet(false, true)) { + Log.w(TAG, "restart skipped — boot already in progress") + return@thread + } + try { + boot() + } finally { + booting.set(false) + } return@thread } } catch (_: InterruptedException) { @@ -228,6 +254,7 @@ class LocoServerService : Service() { Log.e(TAG, "watchdog restart failed", e) _state.value = LocalServerState.Failed(e.message ?: e.toString()) running.set(false) + booting.set(false) stopSelf() return@thread } @@ -237,6 +264,9 @@ class LocoServerService : Service() { private fun stopLocalServer() { running.set(false) + booting.set(false) + bootThread?.interrupt() + bootThread = null watchdogThread?.interrupt() watchdogThread = null cleanupChildren() diff --git a/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt b/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt index 9f47cc2..8e53e08 100644 --- a/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt +++ b/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt @@ -71,6 +71,7 @@ import com.dccbigfred.android.MainActivity import com.dccbigfred.android.R import com.dccbigfred.android.data.ServerPreferences import com.dccbigfred.android.locale.LocalePrefs +import com.dccbigfred.android.models.ModelRow import com.dccbigfred.android.network.CompanionServiceProbe import com.dccbigfred.android.network.CompanionServices import com.dccbigfred.android.server.LocalServerState @@ -190,11 +191,16 @@ fun BigFredApp( LaunchedEffect(Unit) { openLocalWebViewRequests.collect { - val url = when (val s = LocoServerService.state.value) { - is LocalServerState.Running -> s.baseUrl - else -> LocoServerService.LOCAL_BASE_URL + when (val s = LocoServerService.state.value) { + is LocalServerState.Running -> goToWebView(s.baseUrl) + else -> { + // Server not ready yet — show the status screen instead of + // opening an unreachable WebView at 127.0.0.1:8080. + navController.navigate(Routes.LOCAL_SERVER) { + launchSingleTop = true + } + } } - goToWebView(url) } } @@ -608,6 +614,15 @@ fun BigFredApp( composable(Routes.MODELS) { ModelsCatalogScreen( onBack = { navController.popBackStack() }, + onAddToMyVehicles = { row -> + val entity = MyVehiclesViewModel.fromModelRow( + row, + uuid = java.util.UUID.randomUUID().toString(), + ) + scope.launch { + app.localVehicleRepository.upsert(entity) + } + }, ) } composable(Routes.MY_VEHICLES) { From cc5da9eeaffa61afa37d8b804cf6c816164458d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20K=C4=99ska?= <372403+keskad@users.noreply.github.com> Date: Thu, 30 Jul 2026 10:50:27 +0200 Subject: [PATCH 12/17] fix(android): make local server restart atomic Use a single foreground-service restart action so cleanup and boot cannot race through stopSelf, and move blocking process shutdown off the service main thread. Co-authored-by: Cursor --- .../android/server/LocoServerService.kt | 135 +++++++++++++----- .../ui/localserver/LocalServerStatusScreen.kt | 5 +- 2 files changed, 99 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt b/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt index 41b2072..2ad03f5 100644 --- a/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt +++ b/app/src/main/java/com/dccbigfred/android/server/LocoServerService.kt @@ -47,45 +47,83 @@ class LocoServerService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { when (intent?.action) { ACTION_STOP -> { - stopLocalServer() - stopSelf() + stopLocalServer(startId) return START_NOT_STICKY } + ACTION_RESTART -> restartLocalServer() ACTION_START, null -> { - if (booting.compareAndSet(false, true)) { - _state.value = LocalServerState.Starting - try { - startForegroundNotification() - } catch (e: Exception) { - Log.e(TAG, "startForeground failed", e) - booting.set(false) - _state.value = LocalServerState.Failed(e.message ?: e.toString()) - stopSelf() - return START_NOT_STICKY - } - acquireWakeLock() - bootThread = thread(name = "loco-server-boot", isDaemon = true) { - try { - boot() - } catch (e: InterruptedException) { - // stop requested during boot — cleanup already done by stopLocalServer - Log.i(TAG, "boot interrupted, stopping") - } catch (e: Exception) { - Log.e(TAG, "local server start failed", e) - cleanupChildren() - releaseWakeLock() - _state.value = LocalServerState.Failed(e.message ?: e.toString()) - stopSelf() - } finally { - booting.set(false) - } - } + if (!running.get()) { + startLocalServer() } } } return START_STICKY } + private fun startLocalServer() { + if (!booting.compareAndSet(false, true)) return + _state.value = LocalServerState.Starting + try { + startForegroundNotification() + } catch (e: Exception) { + Log.e(TAG, "startForeground failed", e) + booting.set(false) + _state.value = LocalServerState.Failed(e.message ?: e.toString()) + stopSelf() + return + } + acquireWakeLock() + launchBoot(restart = false) + } + + private fun restartLocalServer() { + if (!booting.compareAndSet(false, true)) return + _state.value = LocalServerState.Starting + try { + startForegroundNotification() + } catch (e: Exception) { + Log.e(TAG, "startForeground failed during restart", e) + booting.set(false) + _state.value = LocalServerState.Failed(e.message ?: e.toString()) + stopSelf() + return + } + acquireWakeLock() + launchBoot(restart = true) + } + + private fun launchBoot(restart: Boolean) { + bootThread = thread( + name = if (restart) "loco-server-restart" else "loco-server-boot", + isDaemon = true, + ) { + try { + if (restart) { + running.set(false) + watchdogThread?.interrupt() + watchdogThread = null + cleanupChildren() + ensureBootActive() + } + boot() + } catch (e: InterruptedException) { + Log.i(TAG, "boot interrupted, stopping") + cleanupChildren() + } catch (e: Exception) { + Log.e(TAG, "local server start failed", e) + cleanupChildren() + releaseWakeLock() + _state.value = LocalServerState.Failed(e.message ?: e.toString()) + stopSelf() + } finally { + if (bootThread === Thread.currentThread()) { + bootThread = null + } + booting.set(false) + } + } + } + override fun onDestroy() { cleanupChildren() releaseWakeLock() @@ -104,12 +142,14 @@ class LocoServerService : Service() { private fun boot() { _state.value = LocalServerState.Starting + ensureBootActive() val paths = LocalServerPaths.from(this) paths.ensureDirs() ProcessOrphanReaper.reap(paths.locoServerPid, NativeBinaries.LOCO_SERVER) ProcessOrphanReaper.reap(paths.valkeyPid, NativeBinaries.VALKEY) ProcessOrphanReaper.reap(paths.supervisordPid, NativeBinaries.SUPERVISORD) + ensureBootActive() if (ProcessOrphanReaper.isPortOpen("127.0.0.1", HTTP_PORT) && ProcessOrphanReaper.readPid(paths.locoServerPid) == null @@ -145,6 +185,7 @@ class LocoServerService : Service() { bootUnmanagedValkey(paths, valkeyBin) } + ensureBootActive() val locoLog = File(paths.logsDir, "loco-server.log") val locoArgs = mutableListOf( locoBin.absolutePath, @@ -173,6 +214,7 @@ class LocoServerService : Service() { .redirectOutput(ProcessBuilder.Redirect.appendTo(locoLog)) .also { it.environment().clear(); it.environment().putAll(env) } .start() + ensureBootActive() ProcessOrphanReaper.writePid(paths.locoServerPid, processPid(locoProcess!!)) waitForHttpReady(45_000) @@ -188,6 +230,7 @@ class LocoServerService : Service() { } private fun bootUnmanagedValkey(paths: LocalServerPaths, valkeyBin: File) { + ensureBootActive() val valkeyLog = File(paths.logsDir, "valkey.log") valkeyProcess = ProcessBuilder( valkeyBin.absolutePath, @@ -201,10 +244,17 @@ class LocoServerService : Service() { ).redirectErrorStream(true) .redirectOutput(ProcessBuilder.Redirect.appendTo(valkeyLog)) .start() + ensureBootActive() ProcessOrphanReaper.writePid(paths.valkeyPid, processPid(valkeyProcess!!)) waitForPort("127.0.0.1", REDIS_PORT, 15_000) } + private fun ensureBootActive() { + if (Thread.currentThread().isInterrupted || !booting.get()) { + throw InterruptedException("local server boot cancelled") + } + } + private fun waitForPort(host: String, port: Int, timeoutMs: Long) { val deadline = System.currentTimeMillis() + timeoutMs while (System.currentTimeMillis() < deadline) { @@ -262,21 +312,21 @@ class LocoServerService : Service() { } } - private fun stopLocalServer() { + private fun stopLocalServer(startId: Int) { running.set(false) booting.set(false) bootThread?.interrupt() - bootThread = null watchdogThread?.interrupt() watchdogThread = null - cleanupChildren() - val paths = LocalServerPaths.from(this) - paths.locoServerPid.delete() - paths.valkeyPid.delete() - paths.supervisordPid.delete() _state.value = LocalServerState.Stopped + thread(name = "loco-server-stop", isDaemon = true) { + cleanupChildren() + releaseWakeLock() + stopSelf(startId) + } } + @Synchronized private fun cleanupChildren() { listOf(locoProcess, valkeyProcess).forEach { proc -> proc ?: return@forEach @@ -391,6 +441,7 @@ class LocoServerService : Service() { const val EXTRA_OPEN_LOCAL_WEBVIEW = "com.dccbigfred.android.OPEN_LOCAL_WEBVIEW" const val ACTION_START = "com.dccbigfred.android.server.START" const val ACTION_STOP = "com.dccbigfred.android.server.STOP" + const val ACTION_RESTART = "com.dccbigfred.android.server.RESTART" private val _state = MutableStateFlow(LocalServerState.Stopped) val state: StateFlow = _state.asStateFlow() @@ -415,6 +466,16 @@ class LocoServerService : Service() { context.startService(intent) } + fun restart(context: Context) { + val intent = Intent(context, LocoServerService::class.java).setAction(ACTION_RESTART) + try { + context.startForegroundService(intent) + } catch (e: Exception) { + Log.e(TAG, "restart foreground service failed", e) + _state.value = LocalServerState.Failed(e.message ?: e.toString()) + } + } + fun isLocalUrl(url: String?): Boolean = url != null && (url.contains("127.0.0.1") || url.contains("localhost")) } diff --git a/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerStatusScreen.kt b/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerStatusScreen.kt index b717f71..ecb23c0 100644 --- a/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerStatusScreen.kt +++ b/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerStatusScreen.kt @@ -131,10 +131,7 @@ fun LocalServerStatusScreen( } OutlinedButton( - onClick = { - LocoServerService.stop(context) - LocoServerService.start(context) - }, + onClick = { LocoServerService.restart(context) }, modifier = Modifier.fillMaxWidth(), enabled = state !is LocalServerState.Starting, ) { From ae5c1e603eb9d219b3e820169eaba8cb0eec539d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20K=C4=99ska?= <372403+keskad@users.noreply.github.com> Date: Thu, 30 Jul 2026 20:58:26 +0200 Subject: [PATCH 13/17] Keep WebView alive across rotation and restore SPA route on recreate. Wait for DataStore before bootstrap, prefer WebView when a server URL exists, persist the last SPA URL, lock auto-rotate, and expose manual portrait/landscape toggle via the JS bridge. Co-authored-by: Cursor --- app/src/main/AndroidManifest.xml | 3 +- .../com/dccbigfred/android/MainActivity.kt | 17 +++++++++ .../android/ui/navigation/BigFredApp.kt | 36 +++++++++++++++---- .../android/ui/webview/BigFredJsBridge.kt | 7 ++++ .../ui/webview/BigFredWebViewScreen.kt | 31 ++++++++++++++-- 5 files changed, 84 insertions(+), 10 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d1369a2..f26d2b2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -29,7 +29,8 @@ android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" - android:configChanges="orientation|screenSize|keyboard|keyboardHidden|navigation|locale|layoutDirection" + android:screenOrientation="locked" + android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|navigation|locale|layoutDirection" android:windowSoftInputMode="adjustResize"> diff --git a/app/src/main/java/com/dccbigfred/android/MainActivity.kt b/app/src/main/java/com/dccbigfred/android/MainActivity.kt index 05ef74e..db6f4b4 100644 --- a/app/src/main/java/com/dccbigfred/android/MainActivity.kt +++ b/app/src/main/java/com/dccbigfred/android/MainActivity.kt @@ -1,6 +1,8 @@ package com.dccbigfred.android import android.content.Intent +import android.content.pm.ActivityInfo +import android.content.res.Configuration import android.os.Bundle import android.view.KeyEvent import android.view.WindowManager @@ -40,6 +42,21 @@ class MainActivity : AppCompatActivity() { } } + /** + * Manual portrait ↔ landscape toggle for the SPA rotate button. + * Auto-rotate is disabled via android:screenOrientation="locked"; this + * locks to an explicit orientation until the next tap. + */ + fun toggleScreenOrientation() { + val landscape = + resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE + requestedOrientation = if (landscape) { + ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + } else { + ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + } + } + override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) setIntent(intent) diff --git a/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt b/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt index 8e53e08..339a8fe 100644 --- a/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt +++ b/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt @@ -52,6 +52,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -108,7 +109,16 @@ fun BigFredApp( val context = LocalContext.current val app = context.applicationContext as BigFredApplication val prefs = app.serverPreferences - val savedUrl by prefs.serverBaseUrl.collectAsStateWithLifecycle(initialValue = null) + // Distinguish "DataStore not read yet" from "no saved server" so bootstrap + // does not race into Discovery before the persisted URL arrives. + var prefsReady by remember { mutableStateOf(false) } + var savedUrl by remember { mutableStateOf(null) } + LaunchedEffect(prefs) { + prefs.serverBaseUrl.collect { url -> + savedUrl = url + prefsReady = true + } + } val volumeKeysThrottleEnabled by prefs.volumeKeysThrottleEnabled .collectAsStateWithLifecycle(initialValue = ServerPreferences.DEFAULT_VOLUME_KEYS_THROTTLE_ENABLED) val navController = rememberNavController() @@ -117,7 +127,8 @@ fun BigFredApp( val wifiLock = remember { LowLatencyWifiLock(context) } val companionProbe = remember { CompanionServiceProbe() } - var bootstrapped by remember { mutableStateOf(false) } + // Survive Activity recreation so a restored Nav back stack is not overwritten. + var bootstrapped by rememberSaveable { mutableStateOf(false) } var activeUrl by remember { mutableStateOf(null) } var spaWebView by remember { mutableStateOf(null) } var throttleHardwareKeysActive by remember { mutableStateOf(false) } @@ -153,12 +164,20 @@ fun BigFredApp( fun pushLocaleToSpa() { applyLocaleToWebView(spaWebView, LocalePrefs.resolvedWebLocale()) } - LaunchedEffect(Unit) { - if (bootstrapped) return@LaunchedEffect - navController.navigate(Routes.DISCOVERY) { - popUpTo(Routes.BOOTSTRAP) { inclusive = true } - } + LaunchedEffect(prefsReady, savedUrl) { + if (!prefsReady || bootstrapped) return@LaunchedEffect bootstrapped = true + val url = savedUrl + if (url != null) { + activeUrl = url + navController.navigate(Routes.WEBVIEW) { + popUpTo(Routes.BOOTSTRAP) { inclusive = true } + } + } else { + navController.navigate(Routes.DISCOVERY) { + popUpTo(Routes.BOOTSTRAP) { inclusive = true } + } + } } LaunchedEffect(selectedServerUrl) { @@ -512,6 +531,9 @@ fun BigFredApp( onThrottleHardwareKeysActive = { active -> throttleHardwareKeysActive = active }, + onRotateScreen = { + activity?.toggleScreenOrientation() + }, ) } } diff --git a/app/src/main/java/com/dccbigfred/android/ui/webview/BigFredJsBridge.kt b/app/src/main/java/com/dccbigfred/android/ui/webview/BigFredJsBridge.kt index 7821635..954df1d 100644 --- a/app/src/main/java/com/dccbigfred/android/ui/webview/BigFredJsBridge.kt +++ b/app/src/main/java/com/dccbigfred/android/ui/webview/BigFredJsBridge.kt @@ -10,6 +10,7 @@ import com.dccbigfred.android.locale.LocalePrefs class BigFredJsBridge( private val onOpenModelPicker: () -> Unit, private val onThrottleHardwareKeysActive: (Boolean) -> Unit = {}, + private val onRotateScreen: () -> Unit = {}, ) { @JavascriptInterface fun openModelPicker() { @@ -29,4 +30,10 @@ class BigFredJsBridge( fun setThrottleHardwareKeysActive(active: Boolean) { onThrottleHardwareKeysActive(active) } + + /** Toggle locked portrait/landscape (manual — no sensor auto-rotate). */ + @JavascriptInterface + fun rotateScreen() { + onRotateScreen() + } } diff --git a/app/src/main/java/com/dccbigfred/android/ui/webview/BigFredWebViewScreen.kt b/app/src/main/java/com/dccbigfred/android/ui/webview/BigFredWebViewScreen.kt index 32c38bc..b8935aa 100644 --- a/app/src/main/java/com/dccbigfred/android/ui/webview/BigFredWebViewScreen.kt +++ b/app/src/main/java/com/dccbigfred/android/ui/webview/BigFredWebViewScreen.kt @@ -32,6 +32,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -70,10 +71,15 @@ fun BigFredWebViewScreen( baseUrl: String, onWebViewReady: ((WebView?) -> Unit)? = null, onThrottleHardwareKeysActive: ((Boolean) -> Unit)? = null, + onRotateScreen: (() -> Unit)? = null, ) { // Freeze the URL for this WebView session so DataStore / nav recompositions // with the same address do not trigger a reload (which would churn the WS). - val sessionUrl = remember(baseUrl) { baseUrl.trimEnd('/') + "/" } + val sessionBase = remember(baseUrl) { baseUrl.trimEnd('/') } + val sessionUrl = remember(sessionBase) { "$sessionBase/" } + // Survive unavoidable Activity recreation (e.g. process death) so we can + // reload /throttle instead of always starting at "/". + var lastSpaUrl by rememberSaveable(sessionBase) { mutableStateOf(null) } var webView by remember { mutableStateOf(null) } var loading by remember { mutableStateOf(true) } var loadError by remember { mutableStateOf(null) } @@ -82,8 +88,16 @@ fun BigFredWebViewScreen( val openPicker by rememberUpdatedState(newValue = { pickerVisible = true }) val onReady by rememberUpdatedState(onWebViewReady) val onHardwareKeysActive by rememberUpdatedState(onThrottleHardwareKeysActive) + val onRotate by rememberUpdatedState(onRotateScreen) val lifecycleOwner = LocalLifecycleOwner.current + fun rememberSpaUrl(url: String?) { + if (url.isNullOrBlank()) return + if (url == "about:blank") return + if (!url.startsWith(sessionBase)) return + lastSpaUrl = url + } + BackHandler(enabled = pickerVisible) { deliverModelPickResult(webView, null) pickerVisible = false @@ -179,6 +193,9 @@ fun BigFredWebViewScreen( onThrottleHardwareKeysActive = { active -> post { onHardwareKeysActive?.invoke(active) } }, + onRotateScreen = { + post { onRotate?.invoke() } + }, ), "BigFredNativeApp", ) @@ -195,6 +212,7 @@ fun BigFredWebViewScreen( override fun onPageFinished(view: WebView, url: String?) { loading = false canGoBack = view.canGoBack() + rememberSpaUrl(url) view.requestFocus() applyLocaleToWebView(view, LocalePrefs.resolvedWebLocale()) } @@ -206,6 +224,7 @@ fun BigFredWebViewScreen( ) { loading = true loadError = null + rememberSpaUrl(url) // Navigation clears SPA handlers; reclaim system volume until // a throttle surface re-registers. onHardwareKeysActive?.invoke(false) @@ -217,6 +236,7 @@ fun BigFredWebViewScreen( isReload: Boolean, ) { canGoBack = view.canGoBack() + rememberSpaUrl(url) } override fun onReceivedError( @@ -268,7 +288,14 @@ fun BigFredWebViewScreen( } // Tag before load so the first update{} pass does not reload. tag = sessionUrl - loadUrl(sessionUrl) + val restored = lastSpaUrl + val initialUrl = + if (restored != null && restored.startsWith(sessionBase)) { + restored + } else { + sessionUrl + } + loadUrl(initialUrl) webView = this onReady?.invoke(this) requestFocus() From a5c0e80b2c04c9ced57a8aa82df00ef72760858d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20K=C4=99ska?= <372403+keskad@users.noreply.github.com> Date: Thu, 30 Jul 2026 21:14:56 +0200 Subject: [PATCH 14/17] Prefer local loco-server binary in make apk when available. Use ../bigfred/bin/loco-server-android-arm64 for native-prebuilt instead of pulling from GHCR OCI when the file exists. Co-authored-by: Cursor --- Makefile | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9081c7a..1e7b89b 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,7 @@ ASSETS_MODELS := app/src/main/assets/models NATIVE_PREBUILT := native-prebuilt/arm64-v8a LOCO_SO := $(NATIVE_PREBUILT)/libloco-server.so +LOCAL_LOCO_BIN := ../bigfred/bin/loco-server-android-arm64 VALKEY_SO := $(NATIVE_PREBUILT)/libvalkey-server.so SUPERVISORD_SO := $(NATIVE_PREBUILT)/libsupervisord.so SUPERVISORCTL_SO := $(NATIVE_PREBUILT)/libsupervisorctl.so @@ -43,7 +44,7 @@ help: @echo " make test-android Run instrumented tests (device/emulator required)" @echo " make debug Build debug APK → $(APK_DEBUG)" @echo " make import-models Import hydrus models DB + thumbs → $(ASSETS_MODELS)" - @echo " make loco-android Fetch $(LOCO_SO) from $(BIGFRED_OCI_IMAGE):$(BIGFRED_OCI_TAG) (skip if exists; FORCE=1)" + @echo " make loco-android Fetch $(LOCO_SO) (local $(LOCAL_LOCO_BIN) if present, else $(BIGFRED_OCI_IMAGE):$(BIGFRED_OCI_TAG); FORCE=1)" @echo " make valkey-android Fetch $(VALKEY_SO) from $(VALKEY_REPO) latest release (skip if exists; FORCE=1)" @echo " make supervisord-android Fetch supervisord libs from $(SUPERVISORD_REPO) latest release (skip if exists; FORCE=1)" @echo " make clean Clean Gradle build outputs" @@ -78,8 +79,16 @@ endif loco-android: $(LOCO_SO) +ifneq ($(wildcard $(LOCAL_LOCO_BIN)),) +$(LOCO_SO): $(LOCAL_LOCO_BIN) + @mkdir -p "$(NATIVE_PREBUILT)" + @cp "$<" "$@" + @echo "Using local $< → $@" +else $(LOCO_SO): + @mkdir -p "$(NATIVE_PREBUILT)" ./scripts/fetch-ghcr-oras.sh "$(BIGFRED_OCI_IMAGE)" "$(BIGFRED_OCI_TAG)" "$@" main +endif valkey-android: $(VALKEY_SO) From e84f58278aeb24b5f762b92d8f8c121a4eb0dbf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20K=C4=99ska?= <372403+keskad@users.noreply.github.com> Date: Thu, 30 Jul 2026 21:52:13 +0200 Subject: [PATCH 15/17] Clear stale local hub URL when phone-mode server is not running. Skip bootstrap into WebView for a persisted 127.0.0.1 URL unless LocoServerService is Running, and clear that URL when the local server stops. Co-authored-by: Cursor --- .../android/ui/navigation/BigFredApp.kt | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt b/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt index 339a8fe..f4bfabd 100644 --- a/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt +++ b/app/src/main/java/com/dccbigfred/android/ui/navigation/BigFredApp.kt @@ -161,6 +161,9 @@ fun BigFredApp( val selectedServerUrl = activeUrl ?: savedUrl val selectedServerUrlLatest by rememberUpdatedState(selectedServerUrl) + val localServerState by LocoServerService.state.collectAsStateWithLifecycle() + var localStartError by remember { mutableStateOf(null) } + fun pushLocaleToSpa() { applyLocaleToWebView(spaWebView, LocalePrefs.resolvedWebLocale()) } @@ -169,9 +172,21 @@ fun BigFredApp( bootstrapped = true val url = savedUrl if (url != null) { - activeUrl = url - navController.navigate(Routes.WEBVIEW) { - popUpTo(Routes.BOOTSTRAP) { inclusive = true } + // Stale phone-mode hub: URL was persisted while the FGS ran, but the + // process may have been killed / stopped from the notification. + if (LocoServerService.isLocalUrl(url) && + localServerState !is LocalServerState.Running + ) { + prefs.clearServerBaseUrl() + activeUrl = null + navController.navigate(Routes.DISCOVERY) { + popUpTo(Routes.BOOTSTRAP) { inclusive = true } + } + } else { + activeUrl = url + navController.navigate(Routes.WEBVIEW) { + popUpTo(Routes.BOOTSTRAP) { inclusive = true } + } } } else { navController.navigate(Routes.DISCOVERY) { @@ -180,6 +195,26 @@ fun BigFredApp( } } + // Clear persisted 127.0.0.1 when the local server stops (notification stop, + // OS kill after restart, crash) so the next launch does not open a dead WebView. + LaunchedEffect(localServerState) { + if (localServerState is LocalServerState.Running || + localServerState is LocalServerState.Starting + ) { + return@LaunchedEffect + } + val url = activeUrl ?: savedUrl + if (!LocoServerService.isLocalUrl(url)) return@LaunchedEffect + prefs.clearServerBaseUrl() + activeUrl = null + if (currentRoute == Routes.WEBVIEW) { + navController.navigate(Routes.DISCOVERY) { + popUpTo(navController.graph.id) { inclusive = true } + launchSingleTop = true + } + } + } + LaunchedEffect(selectedServerUrl) { val url = selectedServerUrl if (url == null) { @@ -262,9 +297,6 @@ fun BigFredApp( } } - val localServerState by LocoServerService.state.collectAsStateWithLifecycle() - var localStartError by remember { mutableStateOf(null) } - val notificationPermissionLauncher = androidx.activity.compose.rememberLauncherForActivityResult( contract = androidx.activity.result.contract.ActivityResultContracts.RequestPermission(), ) { /* proceed regardless — FGS may still start with silent notifications denied */ } From 70aaac04ef59b01cee05a9c6ee4ca704c0ae48a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20K=C4=99ska?= <372403+keskad@users.noreply.github.com> Date: Thu, 30 Jul 2026 22:01:35 +0200 Subject: [PATCH 16/17] Add multi-phone warning to built-in BigFred intro screen. Warn users not to run several simplified phone hubs on one layout at once. Co-authored-by: Cursor --- .../ui/localserver/LocalServerIntroScreen.kt | 14 ++++++++++++++ app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-pl/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 4 files changed, 17 insertions(+) diff --git a/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerIntroScreen.kt b/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerIntroScreen.kt index 8037708..171dc5f 100644 --- a/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerIntroScreen.kt +++ b/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerIntroScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable @@ -90,6 +91,19 @@ fun LocalServerIntroScreen( Text(stringResource(R.string.local_server_intro_back)) } Spacer(modifier = Modifier.height(16.dp)) + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp), + color = MaterialTheme.colorScheme.errorContainer, + ) { + Text( + text = stringResource(R.string.local_server_intro_multi_warning), + modifier = Modifier.padding(12.dp), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onErrorContainer, + ) + } + Spacer(modifier = Modifier.height(16.dp)) } } } diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 0088ef4..85ae474 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -145,6 +145,7 @@ BigFred ist als zentraler Hub gedacht — ein physisches Gerät neben der Zentrale, an das sich Telefone, WlanMäuse, WiFreds und LongFreds anbinden. Durch die Zentralisierung können mehrere Nutzer gemeinsam fahren; Funktionen wie Totmann und Notbremsung bei Reichweitenverlust sind möglich.\n\nWenn Sie die Hub-Software als BigFred auf dem Telefon starten, erhalten Sie eine funktions- und sicherheitsreduzierte Variante. Sie kann keinen Zug notbremsen, wenn der Akku leer ist, Sie können kein zweites Telefon anbinden und keine WlanMaus, keinen WiFred und keinen LongFred anschließen. Betrachten Sie das als Demo vor dem Kauf von Hardware für BigFred. Zurück Verstanden, trotzdem starten + Schließen Sie nicht mehrere vereinfachte BigFred-Instanzen gleichzeitig an dieselbe Anlage an. Ohne zentrales physisches Gerät nutzen Sie BigFred jeweils nur auf einem Telefon. BigFred auf dem Telefon: aktiv BigFred auf dem Telefon Server stoppen diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 6912acb..718d43d 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -145,6 +145,7 @@ BigFred to z założenia centralny hub, fizyczne urządzenie leżące obok centralki, do którego podłączają się telefony, wlanmausy, wifredy, longfredy. Dzięki centralizacji hub umożliwia sterowanie wielu użytkownikom oraz posiada takie funkcjonalności jak np. czuwak i awaryjne hamowanie przy utracie zasięgu.\n\nUruchamiając oprogramowanie huba — BigFred na telefonie — otrzymasz okrojoną z funkcji i zabezpieczeń wersję. Nie będzie ona w stanie awaryjnie zahamować składu, gdy rozładuje Ci się telefon, nie podłączysz do niej drugiego telefonu, nie podłączysz Wlanmausa, WiFreda ani LongFreda. Potraktuj to jako wersję pokazową przed zakupem urządzenia do uruchomienia BigFreda. Cofnij Rozumiem, uruchom pomimo tego + Nie należy do tej samej makiety podłączać wielu uproszczonych BigFredów na raz. Bez centralnego, fizycznego urządzenia korzystaj z BigFred tylko na jednym telefonie w jednym czasie BigFred na telefonie: działa BigFred na telefonie Zatrzymaj serwer diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 44d2568..f6d744b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -145,6 +145,7 @@ BigFred is designed as a central hub — a physical device next to the command station that phones, WlanMaus units, WiFreds and LongFreds connect to. Centralisation lets multiple users drive together and enables features such as dead-man protection and emergency braking when radio range is lost.\n\nRunning hub software as BigFred on a phone gives you a cut-down build with fewer features and safeguards. It cannot emergency-brake a train if your phone battery dies, you cannot connect a second phone, and you cannot attach a WlanMaus, WiFred or LongFred. Treat it as a demo before buying hardware to run BigFred properly. Back I understand — start anyway + Do not connect multiple simplified BigFred instances to the same layout at once. Without a central physical device, use BigFred on only one phone at a time. BigFred on phone: running BigFred on phone Stop server From 927a891585f3cfbe4154e471fc0c4fbe06266032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20K=C4=99ska?= <372403+keskad@users.noreply.github.com> Date: Thu, 30 Jul 2026 22:02:45 +0200 Subject: [PATCH 17/17] Add orange app bar info notice to built-in BigFred intro. Explain that simplified phone mode is visually distinguished by the orange top bar. Co-authored-by: Cursor --- .../ui/localserver/LocalServerIntroScreen.kt | 13 +++++++++++++ app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-pl/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 4 files changed, 16 insertions(+) diff --git a/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerIntroScreen.kt b/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerIntroScreen.kt index 171dc5f..3b7c058 100644 --- a/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerIntroScreen.kt +++ b/app/src/main/java/com/dccbigfred/android/ui/localserver/LocalServerIntroScreen.kt @@ -103,6 +103,19 @@ fun LocalServerIntroScreen( color = MaterialTheme.colorScheme.onErrorContainer, ) } + Spacer(modifier = Modifier.height(12.dp)) + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp), + color = MaterialTheme.colorScheme.primaryContainer, + ) { + Text( + text = stringResource(R.string.local_server_intro_orange_bar_info), + modifier = Modifier.padding(12.dp), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onPrimaryContainer, + ) + } Spacer(modifier = Modifier.height(16.dp)) } } diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 85ae474..1cc6d32 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -146,6 +146,7 @@ Zurück Verstanden, trotzdem starten Schließen Sie nicht mehrere vereinfachte BigFred-Instanzen gleichzeitig an dieselbe Anlage an. Ohne zentrales physisches Gerät nutzen Sie BigFred jeweils nur auf einem Telefon. + Der vereinfachte BigFred hat eine orangefarbene obere Leiste, um ihn vom BigFred auf einem zentralen physischen Gerät zu unterscheiden. BigFred auf dem Telefon: aktiv BigFred auf dem Telefon Server stoppen diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 718d43d..c17890e 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -146,6 +146,7 @@ Cofnij Rozumiem, uruchom pomimo tego Nie należy do tej samej makiety podłączać wielu uproszczonych BigFredów na raz. Bez centralnego, fizycznego urządzenia korzystaj z BigFred tylko na jednym telefonie w jednym czasie + Uproszczony BigFred ma pomarańczową górną belkę dla odróżnienia od BigFreda, który jest uruchamiany na fizycznym, centralnym urządzeniu BigFred na telefonie: działa BigFred na telefonie Zatrzymaj serwer diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f6d744b..20c3b27 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -146,6 +146,7 @@ Back I understand — start anyway Do not connect multiple simplified BigFred instances to the same layout at once. Without a central physical device, use BigFred on only one phone at a time. + Simplified BigFred has an orange top bar to distinguish it from BigFred running on a central physical device. BigFred on phone: running BigFred on phone Stop server