From 990ff6db57dd0cbc679de5bf99b6bfc546b70a3a Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 12 Jul 2026 14:22:35 +0000 Subject: [PATCH 1/4] fix: use Build.DEVICE instead of Build.MODEL for hostname fallback Build.MODEL returns the OEM marketing name (e.g. 'Poco F8 Ultra') which contains spaces that break hostname-based bucket matching in aw-webui and aw-server. Build.DEVICE returns the device codename (e.g. 'poco_f8_ultra') which is lower-case, underscore-delimited, and safe to use as a hostname. Fixes the class of bug reported in ActivityWatch/aw-android#176 where Xiaomi/Poco devices produced space-containing hostnames. --- mobile/src/main/java/net/activitywatch/android/RustInterface.kt | 2 +- mobile/src/main/java/net/activitywatch/android/SyncInterface.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/java/net/activitywatch/android/RustInterface.kt b/mobile/src/main/java/net/activitywatch/android/RustInterface.kt index cac41c9a..3a011652 100644 --- a/mobile/src/main/java/net/activitywatch/android/RustInterface.kt +++ b/mobile/src/main/java/net/activitywatch/android/RustInterface.kt @@ -173,7 +173,7 @@ class RustInterface(context: Context? = null) { fun getDeviceName(context: Context): String { return Settings.Global.getString(context.contentResolver, Settings.Global.DEVICE_NAME) - ?: android.os.Build.MODEL ?: "Unknown" + ?: android.os.Build.DEVICE ?: "Unknown" } } diff --git a/mobile/src/main/java/net/activitywatch/android/SyncInterface.kt b/mobile/src/main/java/net/activitywatch/android/SyncInterface.kt index a7d9372d..267d3eaf 100644 --- a/mobile/src/main/java/net/activitywatch/android/SyncInterface.kt +++ b/mobile/src/main/java/net/activitywatch/android/SyncInterface.kt @@ -63,7 +63,7 @@ class SyncInterface(context: Context) { return android.provider.Settings.Global.getString( appContext.contentResolver, android.provider.Settings.Global.DEVICE_NAME - ) ?: android.os.Build.MODEL ?: "Unknown" + ) ?: android.os.Build.DEVICE ?: "Unknown" } // Async wrapper for syncPullAll From 0f1cbfd4872a3f4c2d574891701f3f6a8fa37fd6 Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 12 Jul 2026 14:49:14 +0000 Subject: [PATCH 2/4] fix: sanitize hostname to be space-free regardless of source Settings.Global.DEVICE_NAME can itself contain spaces (e.g. 'Poco F8 Ultra') so the previous fix only addressed the fallback path. Sanitize the final hostname by trimming, lowercasing, and replacing spaces with underscores, ensuring bucket hostname matching works even when DEVICE_NAME is configured with a human-readable name. --- .../main/java/net/activitywatch/android/RustInterface.kt | 6 ++++-- .../main/java/net/activitywatch/android/SyncInterface.kt | 7 ++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/mobile/src/main/java/net/activitywatch/android/RustInterface.kt b/mobile/src/main/java/net/activitywatch/android/RustInterface.kt index 3a011652..eef33ab4 100644 --- a/mobile/src/main/java/net/activitywatch/android/RustInterface.kt +++ b/mobile/src/main/java/net/activitywatch/android/RustInterface.kt @@ -172,8 +172,10 @@ class RustInterface(context: Context? = null) { } fun getDeviceName(context: Context): String { - return Settings.Global.getString(context.contentResolver, Settings.Global.DEVICE_NAME) - ?: android.os.Build.DEVICE ?: "Unknown" + val raw = Settings.Global.getString(context.contentResolver, Settings.Global.DEVICE_NAME) + ?: android.os.Build.DEVICE + ?: "unknown" + return raw.trim().replace(' ', '_').lowercase() } } diff --git a/mobile/src/main/java/net/activitywatch/android/SyncInterface.kt b/mobile/src/main/java/net/activitywatch/android/SyncInterface.kt index 267d3eaf..748e7ed6 100644 --- a/mobile/src/main/java/net/activitywatch/android/SyncInterface.kt +++ b/mobile/src/main/java/net/activitywatch/android/SyncInterface.kt @@ -60,10 +60,11 @@ class SyncInterface(context: Context) { external fun getSyncDir(): String private fun getDeviceName(): String { - return android.provider.Settings.Global.getString( - appContext.contentResolver, + val raw = android.provider.Settings.Global.getString( + appContext.contentResolver, android.provider.Settings.Global.DEVICE_NAME - ) ?: android.os.Build.DEVICE ?: "Unknown" + ) ?: android.os.Build.DEVICE ?: "unknown" + return raw.trim().replace(' ', '_').lowercase() } // Async wrapper for syncPullAll From f9ea7c11aa9198e5ec9a1fa47327dec18337e80d Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 12 Jul 2026 14:58:12 +0000 Subject: [PATCH 3/4] fix: strengthen hostname sanitization to strip all non-hostname chars Use strict regex [^a-z0-9_-]+ instead of only space replacement, so configured device names with quotes or other special chars don't break bucket JSON. Also guard whitespace-only DEVICE_NAME with takeIf so it falls through to Build.DEVICE rather than resolving to empty string. --- .../main/java/net/activitywatch/android/RustInterface.kt | 6 +++++- .../main/java/net/activitywatch/android/SyncInterface.kt | 9 +++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/mobile/src/main/java/net/activitywatch/android/RustInterface.kt b/mobile/src/main/java/net/activitywatch/android/RustInterface.kt index eef33ab4..2de1988d 100644 --- a/mobile/src/main/java/net/activitywatch/android/RustInterface.kt +++ b/mobile/src/main/java/net/activitywatch/android/RustInterface.kt @@ -175,7 +175,11 @@ class RustInterface(context: Context? = null) { val raw = Settings.Global.getString(context.contentResolver, Settings.Global.DEVICE_NAME) ?: android.os.Build.DEVICE ?: "unknown" - return raw.trim().replace(' ', '_').lowercase() + return raw.trim() + .lowercase(java.util.Locale.ROOT) + .replace(Regex("[^a-z0-9_-]+"), "_") + .trim('_') + .ifEmpty { "unknown" } } } diff --git a/mobile/src/main/java/net/activitywatch/android/SyncInterface.kt b/mobile/src/main/java/net/activitywatch/android/SyncInterface.kt index 748e7ed6..481ae8ab 100644 --- a/mobile/src/main/java/net/activitywatch/android/SyncInterface.kt +++ b/mobile/src/main/java/net/activitywatch/android/SyncInterface.kt @@ -63,8 +63,13 @@ class SyncInterface(context: Context) { val raw = android.provider.Settings.Global.getString( appContext.contentResolver, android.provider.Settings.Global.DEVICE_NAME - ) ?: android.os.Build.DEVICE ?: "unknown" - return raw.trim().replace(' ', '_').lowercase() + )?.trim()?.takeIf { it.isNotEmpty() } + ?: android.os.Build.DEVICE ?: "unknown" + return raw.trim() + .lowercase(java.util.Locale.ROOT) + .replace(Regex("[^a-z0-9_-]+"), "_") + .trim('_') + .ifEmpty { "unknown" } } // Async wrapper for syncPullAll From 5fff8bb62e6a041369093afc227bffa7820e02c4 Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 12 Jul 2026 15:32:55 +0000 Subject: [PATCH 4/4] fix: filter blank DEVICE_NAME before falling back to Build.DEVICE in RustInterface --- mobile/src/main/java/net/activitywatch/android/RustInterface.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mobile/src/main/java/net/activitywatch/android/RustInterface.kt b/mobile/src/main/java/net/activitywatch/android/RustInterface.kt index 2de1988d..18196bb6 100644 --- a/mobile/src/main/java/net/activitywatch/android/RustInterface.kt +++ b/mobile/src/main/java/net/activitywatch/android/RustInterface.kt @@ -173,6 +173,8 @@ class RustInterface(context: Context? = null) { fun getDeviceName(context: Context): String { val raw = Settings.Global.getString(context.contentResolver, Settings.Global.DEVICE_NAME) + ?.trim() + ?.takeIf { it.isNotEmpty() } ?: android.os.Build.DEVICE ?: "unknown" return raw.trim()