Skip to content

Bugs found while taking v0.14.2 screenshots on an emulator (incl. data lost after first restart) #321

Description

@ErikBjare

Found while scripting marketing screenshots of v0.14.2 (release APK, universal) on an Android 16 emulator (AVD on CT 102, fresh install each run, demo data imported through the app's REST API at localhost:5600 with the API key from the app's config.toml).

@TimeToBuildBob this one is yours (assigned by Erik). Item 1 first; it may affect real users.

  • 1. Data written right after a fresh install is lost on the next app restart. Events and settings imported via the API vanished after the first app restart in the first 1–2 attempts of all 3 fresh runs; once the device had settled, imports survived restarts. Log buffer had rotated, so no root cause yet. Suspects: DB opened/committed before WAL checkpoint and the process killed, or the DB being recreated on first real start. Repro: fresh install → finish onboarding → POST a bucket + events → force-stop app → reopen → check /api/0/buckets/. Done when imported data survives a force-stop immediately after first launch.
  • 2. ANR on a fresh boot: "ActivityWatch isn't responding", main thread stuck in MainActivity.onResume while starting a thread. Move the blocking work off the main thread.
  • 3. Desktop-only tabs on Android: the bundled web UI shows the Window/Browser/Editor tabs, which only say "missing data from a required watcher" on a phone. Default Android to the Android views.
  • 4. Week view starts on the given date, not on Monday (/week/<date>), probably the same root cause as UI bugs found while generating v0.14 marketing screenshots aw-webui#1058 item 1 (period not snapped to its start).
  • 5. Native sync settings screen ignores dark mode.
  • 6. With gesture navigation, back-swipe finishes MainActivity instead of opening the drawer or going back in the WebView.
  • 7. Fresh install shows "The saved aw-notify setting has an unsupported format" in Settings: already fixed upstream in fix(settings): treat an unset aw-notify setting as defaults aw-webui#1043, needs the web UI bump.
  • 8. Multi-device (@all, or several hosts) returns 0 s in the v0.14.2 web UI even with synced desktop buckets present; check after the web UI bump.

Screenshot script (re-runnable, ~8 min): CT 102 /root/aw-screenshots/android_screenshots.py --wipe.

Activity

  1. TimeToBuildBob commented on Oct 6, 2026

    @TimeToBuildBob
    Contributor

    Acknowledged — triaging the 8 items by priority:

    Critical (affects real users):

    • 1. Data loss on first restart — highest priority. Likely a WAL checkpoint race or the DB being recreated on first real start before the imported data flushes. Will investigate the DB init path in MainActivity/AppDatabase. Repro: fresh install → onboarding → POST buckets+events → force-stop → reopen → check /api/0/buckets/.
    • 2. ANR on fresh boot — main thread blocked in MainActivity.onResume while starting a thread. Will move blocking work off the main thread.

    Quick wins:

    Medium effort:

    • 3. Desktop-only tabs on Android — need to check how the webui detects the platform and suppresses Window/Browser/Editor tabs on Android.
    • 6. Back-swipe finishes MainActivity — need to intercept onBackPressed for gesture nav and either open the drawer or dispatch the event to the WebView.

    Depends on webui work:

    Starting with item 1 (data loss) since it's the most likely to affect real users. Will post findings and PRs here.

  2. ErikBjare commented on Oct 6, 2026

    @ErikBjare
    MemberAuthor

    Update from a second run (logs captured this time):

    • Item 1 root cause: on a fresh install the embedded aw-server-rust can be started twice; both run DB setup and one crashes with table key_value already exists. After that the API errors or silently drops writes on later starts. Clearing app data and redoing onboarding recovers it. Fix: make server start idempotent (single-instance guard) and make the migration tolerant (CREATE TABLE IF NOT EXISTS / inside a transaction).
    • New: sync status warning on every phone. "Found 0 remote db files … Zero peers …" shows on push-only phones because (a) the filter meant to hide it matches zero peers but the message says Zero peers (case), and (b) it looks for peer dbs in the app's private dir instead of the SAF folder the user picked.
    • New: pushed sync db is named test.db (<host>/<device-id>/test.db), likely a leftover testing name.
  3. TimeToBuildBob commented on Oct 6, 2026

    @TimeToBuildBob
    Contributor

    Thanks for the second-run findings — this is exactly the context needed to fix item 1 properly.

    Item 1 (data loss) — root cause confirmed:
    The double-start path makes sense as the trigger. Fix plan:

    1. Single-instance guard in Android — check if server process is already running before spawning a second. Can use a port check or a PID file in app data dir.
    2. Idempotent schema migrations in aw-server-rust — CREATE TABLE IF NOT EXISTS / wrapping in a transaction with IF NOT EXISTS guards on all DDL. This is the safer fix since it protects against any double-init scenario, not just this one path.

    Will open separate PRs for each (Android-side guard + aw-server-rust migration hardening).

    New items from second run:

    • Sync status warning (case + path bug) — will fix the zero peers → Zero peers case sensitivity and the SAF path lookup. This is a pure Android fix.
    • test.db filename — sounds like a leftover from development. Will track down where the filename is set and replace with <device_id>.db or similar.

    Adding these 3 new items to the tracking list. I'll post links here as PRs open.

  4. TimeToBuildBob commented on Oct 6, 2026

    @TimeToBuildBob
    Contributor

    Status update — linked PRs so far:

    aw-server-rust side (data loss fix)

    Android side (data loss fix)

    • The Android-side single-instance guard (prevent the second startServer() call) is the remaining blocker. I need to add a port-occupancy check before RustInterface.startServer() to short-circuit rather than queue a second start. Will open a follow-up PR.

    aw-webui bugs (items 4, Trends, Work report)

    Other items opened earlier


    Still pending (will open PRs):

    • Android single-instance guard (the Android half of the data loss fix above)
    • Sync status "Zero peers" case-sensitivity + SAF path lookup
    • test.db → <device_id>.db rename in sync push
    • ANR on fresh boot (main thread blocking in onResume)
  5. TimeToBuildBob commented on Oct 6, 2026

    @TimeToBuildBob
    Contributor

    One more from today's investigation: the 'zero peers' warning filter in SyncSettingsActivity.kt was broken due to a case mismatch — the code checked for lowercase "zero peers" but aw-server-rust emits "Zero peers" (capital Z). PR #323 fixes it with a one-line .lowercase() call.

  6. TimeToBuildBob commented on Oct 6, 2026

    @TimeToBuildBob
    Contributor

    Android single-instance guard opened: #324

    Identified and fixed the race that allowed two concurrent onStartCommand invocations to both pass the !serverStarted check before either set the flag:

    • @Volatile on serverStarted so readers outside the synchronized block always see fresh values
    • synchronized(this) wraps the entire check-probe-set sequence in startServerTask(), making the guard atomic

    Complements ActivityWatch/aw-server-rust#792 (idempotent migration, merged) — together they prevent both the double-start and the crash if a double-start somehow still occurs.

  7. TimeToBuildBob commented on Oct 6, 2026

    @TimeToBuildBob
    Contributor

    Item 2 (ANR on fresh boot) — PR #325 open.

    Root cause: lifecycleScope.launch {} uses Dispatchers.Main.immediate, running the coroutine body inline on the main thread. sendHeartbeatsSuspend() accessed lazy sessionWatcher, constructing SessionEventWatcher. SessionEventWatcher.rust was eagerly initialised — its OffThreadInit calls Thread.start() immediately. On a heavily-loaded fresh boot Thread.start() can block long enough to ANR.

    Fix: (a) make SessionEventWatcher.rust lazy (same pattern as UsageStatsWatcher.rust), and (b) add withContext(Dispatchers.IO) in sendHeartbeatsSuspend() so the lazy init always happens off the main thread.

  8. TimeToBuildBob commented on Oct 6, 2026

    @TimeToBuildBob
    Contributor

    Following up on items 2 and 5:

    Item 2 — ANR on fresh boot (PR #325)

    Root cause: lifecycleScope.launch {} runs on Dispatchers.Main.immediate, which executes the coroutine body inline on the UI thread until the first suspension point. Accessing the lazy sessionWatcher triggered SessionEventWatcher construction on the main thread, which in turn called OffThreadInit → Thread.start(). On a fresh boot under high load, Thread.start() can block the UI thread long enough to ANR.

    Two-part fix in #325:

    1. Made SessionEventWatcher.rust a by lazy property (matching the pattern already in UsageStatsWatcher) so construction of OffThreadInit is deferred.
    2. Added withContext(Dispatchers.IO) in UsageStatsWatcher.sendHeartbeatsSuspend() before touching sessionWatcher, ensuring all lazy construction and JNI init happens off the main thread even when the caller is on Dispatchers.Main.immediate.

    Item 5 — Sync settings ignores dark mode (PR #326)

    Root cause: applySafeWindowInsets() always called applySystemBarAppearance(darkContent = false), hardcoding a white window background for all native screens. In dark mode the DayNight theme resolved text colours to white → white text on white background.

    Fix in #326: Added followNightMode: Boolean = false parameter to applySafeWindowInsets(). SyncSettingsActivity now passes followNightMode = true. The layout already uses only DayNight theme attributes, so no layout changes were needed. Default is false so MainActivity (which drives its own appearance from the embedded web UI) is unaffected.

  9. TimeToBuildBob commented on Oct 6, 2026

    @TimeToBuildBob
    Contributor

    Item 6 (back-swipe finishes MainActivity): #327. Back now closes the drawer if it's open, otherwise goes back in the WebView's history, and only finishes the activity when there's no history left. 3 new unit tests. I haven't checked it on a device yet; the PR lists the emulator steps.

    test.db filename: this isn't a one-line rename, so I haven't opened a PR. The name is set in aw-server-rust (setup_local_remote, aw-sync/src/sync.rs), which desktop and Android share. Two things get in the way:

    The safe order is:

    1. aw-server-rust renames test.db → sync.db together with its -wal/-shm sidecars, before the datastore opens.
    2. The Android mirror deletes *.db files in its own <host>/<device_id>/ that no longer exist locally.
    3. Both land in the same Android release.

    aw-sync status already warns about the name (status.rs), so nothing is broken in the meantime. Doing it in that order unless you'd rather keep test.db until sync v2 replaces the format (ActivityWatch/aw-server-rust#691).

    Sync warning, part (b): Android reads peers from its private sync dir because the SAF mirror only goes outward. Pulling peers from the SAF folder is new inbound-sync work, not a filter fix. For push-only phones, the case fix in #323 is enough to hide the warning.

    Conflicting PRs for item 5: #309 locks Auth/Sync/Onboarding to a light-only theme, while #326 makes Sync settings follow dark mode. Your report asks for dark mode, so #326 is the right direction for Sync. #309 should drop SyncSettingsActivity, or be closed if you want those screens to follow DayNight as well. Only one of them should merge.

    Item status:

    Item Status
    1. Data loss after fresh install ActivityWatch/aw-server-rust#792 (merged) + #324
    2. ANR on fresh boot #325
    3. Desktop-only tabs on Android ActivityWatch/aw-webui#1061: root cause is that --os=android was never parsed under the ESM vue.config.js, so every Android build shipped desktop defaults. Needs a web UI bump in aw-android after merge
    4. Week view not snapped to Monday ActivityWatch/aw-webui#1059
    5. Sync settings ignore dark mode #326 (see conflict above)
    6. Back-swipe finishes MainActivity #327
    7. Unsupported aw-notify toast needs the web UI bump (same bump as item 3)
    8. Multi-device shows 0 s check after the web UI bump
    Sync "Zero peers" warning #323
    test.db filename plan above
  10. TimeToBuildBob commented on Oct 9, 2026

    @TimeToBuildBob
    Contributor

    Item 3 (desktop tabs on Android): ActivityWatch/aw-webui#1061 (merged) fixes fresh installs only. Builds before it had already stored the desktop views on the server on any settings change, so existing users would keep the desktop tabs. ActivityWatch/aw-webui#1088 shows the Android views on Android when the stored views are an unmodified desktop default. Views the user edited are kept.

    Neither fix is in the app yet. aw-android pins aw-server-rust 70ba50d, whose aw-webui is 38 commits behind #1061. Items 3, 7 and 8 need the next aw-webui bump in aw-server-rust, then an aw-server-rust bump in aw-android.

  11. TimeToBuildBob commented on Oct 10, 2026

    @TimeToBuildBob
    Contributor

    #356 — aw-server-rust bump (items 3, 7, 8 + data-loss server layer)

    #356 bumps aw-server-rust from c535d77 to 795b264 (9 commits).

    Key changes:

    • Items 3, 7, 8 (desktop tabs / aw-notify toast / multi-device 0 s) — webui bump (3b1c444) lands aw-webui#1061 + #1088: Android-only views on fresh install and stored desktop-default views upgraded to Android views.
    • Item 1 (data loss) server layer — fix(android): open the JNI datastore exactly once (aw-server-rust#801) complements fix(server): make startServerTask() single-instance guard atomic #324 + aw-server-rust#792.

    Updated item status after #356 merges: items 3, 7, 8 closed; item 1 has all three layers in place.

  12. TimeToBuildBob commented on Oct 10, 2026

    @TimeToBuildBob
    Contributor

    Final item status (2026-10-10):

    Item Status
    1. Data loss after fresh install ✅ #324 (atomic guard, MERGED) + aw-server-rust#792 (idempotent migration, MERGED) + #356 (JNI once-only, pending merge)
    2. ANR on fresh boot ✅ #325 MERGED
    3. Desktop-only tabs on Android ⏳ #356 pending merge (webui bump with #1061+#1088)
    4. Week view not snapped to Monday ✅ aw-webui#1059 MERGED (picked up by #356 bump)
    5. Sync settings ignore dark mode ✅ #326 MERGED
    6. Back-swipe finishes MainActivity ✅ #327 MERGED
    7. Unsupported aw-notify toast ⏳ #356 pending merge
    8. Multi-device shows 0 s ⏳ #356 pending merge (verify after)
    Sync "Zero peers" warning ✅ #323 MERGED
    test.db filename Plan in earlier comment; deferred to sync v2

    Items 3, 7, 8 and the final data-loss layer all land with #356. 9/10 items resolved or pending one merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions