Three additional fixes found via live device logs:
1. Startup race window: FileObserver fires immediately after
startWatching() before catchupScan coroutine runs, starting a 5s
debounce with cooldown=0. Fixed by setting a 15s startup cooldown
in watchPath() BEFORE calling watchDirRecursive.
2. Stale debounce bypass: debounce job started with cooldown=0 fires
5s later even after catchupScan has already set cooldown and started
a catchup sync. Fixed by re-checking cooldown after the 5s delay
and aborting if already active.
3. Debounce not cancelled by catchupScan: if a debounce was queued
before catchupScan ran, catchupScan would enqueue a catchup sync
AND the old debounce would fire 5s later enqueuing a second sync.
Fixed by cancelling pending debounce in catchupScan before enqueue.
Icon: four thick arcs (blue/red/green/orange) in a 4-way pinwheel
with over/under ordering. White sync-arrow circle at center.
Pure black background.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three root causes found via live logcat on device:
1. concurrent refresh() race: onStartCommand received twice causes two
refresh() coroutines to run in parallel, doubling FileObserver and
catchupScan registrations. Fixed with Mutex.withLock on refresh().
2. catchupScan no cooldown: catchup syncs write files but never set
syncCooldownUntil, so every written file immediately re-triggers
onChangeDetected. Fixed by setting cooldown before enqueue and
watching work completion same as onChangeDetected does.
3. CancellationException caught silently: exception handler
catch(_: Exception) was catching CancellationException and resetting
cooldown to 0L, re-opening the loop. Fixed by rethrowing
CancellationException and setting 60s cooldown on other errors.
Icon: interlocked rings (blue/red/green/orange) with sync arrow at
center, pure black background — matches reference image.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SyncEngine: self-healing stale folder state detection (isRetry) wipes
orphaned SyncFileStateEntity records when localPath changes without a
pair re-save — prevents repeated DELETE_REMOTE on 32 old files
- SyncEngine: second-precision mtime comparison (/ 1000 / .epochSecond)
eliminates phantom localChanged=true from FAT32/WebDAV precision mismatch
- FileWatchService: syncCooldownUntil map suppresses FileObserver events
for 120s after sync starts and 60s after it finishes, breaking the
download→FileObserver→sync→download feedback loop
- Icon: three bold teardrop shapes (teal/red/amber) rotated 0/120/240°
on dark charcoal background with white cloud at intersection
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Icon: three identical parallel arcing arrows (same bezier curve, same blue-to-teal
gradient #64C8FF→#32EDBB, same arrowhead geometry) — visually cohesive and clearly
visible against the near-black background.
FileWatchService: FileObserver is now recursive — watchDirRecursive() creates an
observer for each subdirectory at startup, and adds new watchers when CREATE events
produce new directories. Fixes files added to subdirectories not being detected.
FilesViewModel: openFile/shareFile now fall back to download-then-open when the file
is absent locally. AccountRepository + ProviderFactory injected; downloads to
context.cacheDir/syncflow_open/ with isDownloading state. Path traversal guard added
(reject relativePath containing ".."). file_paths.xml gains cache-path entry.
WebDavProvider: path-traversal guard in parsePropfind — skip any server-returned
filename containing "..", "/" or "\". Replace android.util.Log with Timber so debug
logs are stripped from release builds.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- FilesScreen: per-file context menu (Open, Share, Rename, Delete), rename dialog,
delete confirmation, FileProvider-based open/share intents, Snackbar error feedback
- FilesViewModel: FileAction sealed class + SharedFlow; openFile, shareFile,
deleteFile, renameFile with DB cleanup; resolveFile handles SAF primary: URIs
- FileWatchService: stopWithTask=false keeps watcher alive after app swipe-away;
catchupScan on startup detects changes missed while service was not running;
SyncFileStateDao injected; FileObserver used for real-path SAF URIs
- BootReceiver: handles MY_PACKAGE_REPLACED to restart service after app update
- file_paths.xml: added external-path so FileProvider can serve /storage/emulated/0 files
- ic_launcher_foreground: three curved stroke-based arrows (quadratic bezier, round caps)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Request POST_NOTIFICATIONS permission at runtime in MainActivity (primary fix
for notifications never appearing on Android 13+ phones including Android 16)
- Register all 4 notification channels eagerly in SyncFlowApp.onCreate() instead
of lazily inside workers
- Add FOREGROUND_SERVICE_SHORT_SERVICE permission + shortService foreground type
for Android 16 foreground service compatibility
- Add global activity Log tab (new tab 2 in main nav) showing all sync events
across all pairs, grouped by date with pair name, event icon, and file detail
- Fix FileWatchService ON_CHANGE detection: ContentObserver on SAF tree URIs only
fires for SAF-API writes, not raw filesystem writes. Now resolves primary:/*
tree URIs to /storage/emulated/0/* and uses FileObserver for reliable detection
- Bump version to 1.0.21 (build 22)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Sync icon now rotates (CSS-style spin) in StatusPill, StatusBanner,
and card sync button whenever status is SYNCING
- Launcher icon redesigned: indigo→violet→cyan gradient background,
upload arrow fades white→sky-blue, download arrow fades white→violet,
soft glow ring behind arrows
- Fix ON_CHANGE not triggering: FileWatchService.start() now called
from AddPairViewModel.save() so pairs created with ON_CHANGE
immediately begin watching without needing a toggle or reboot
- Fix FileWatch notification hidden: IMPORTANCE_MIN → IMPORTANCE_LOW
so the "Watching N folders" notification shows in the shade
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add FileWatchService for real-time ON_CHANGE sync (FileObserver for
direct paths, ContentObserver for SAF content:// URIs), 5s debounce
- Fix remote browser stuck spinner: cancel in-flight jobs on navigation,
reset entries immediately, add Retry button on error
- Fix browser reuse bug: LaunchedEffect key now includes initialPath
- Fix WebDavProvider: rethrow XML parse errors (no more silent Empty
folder) and URL-decode file names from href
- Notifications now use BigTextStyle showing per-file-type counts
(Uploaded/Downloaded/Deleted) matching Autosync notification style
- Wire FileWatchService into BootReceiver and HomeViewModel toggle
- Register FileWatchService in AndroidManifest
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>