Skip to content

feat(sync): disable automatic sync by default#184

Merged
ErikBjare merged 2 commits into
ActivityWatch:masterfrom
TimeToBuildBob:sync-disabled-by-default
Jul 12, 2026
Merged

feat(sync): disable automatic sync by default#184
ErikBjare merged 2 commits into
ActivityWatch:masterfrom
TimeToBuildBob:sync-disabled-by-default

Conversation

@TimeToBuildBob

Copy link
Copy Markdown
Contributor

Background

Closes ActivityWatch/activitywatch#1357 (partial — addresses the default-off requirement; Storage Access Framework setup flow is a follow-up).

Problem

SyncScheduler fires every 15 minutes unconditionally, but Android sync is effectively broken for most users:

Changes

AWPreferences.kt — adds isSyncEnabled() (default: false) and setSyncEnabled(Boolean). The false default means new installs won't start the scheduler automatically. setSyncEnabled(true) is the hook for a future settings toggle or Storage Access Framework setup flow.

BackgroundService.kt — gates syncScheduler.start() on prefs.isSyncEnabled(). If disabled, logs a message instead of starting the 15-minute chain.

SyncAlarmReceiver.kt — gates the AlarmManager fallback path on the same preference. Without this, an alarm registered before a downgrade/reinstall could still fire sync even with the new default.

What's not in this PR

  • Settings UI toggle (follow-up — needs a settings screen)
  • Storage Access Framework directory picker (follow-up — larger change)
  • Docs update at docs.activitywatch.net/en/latest/syncing.html (follow-up in main repo)

Testing

  • Fresh install: AWPreferences.isSyncEnabled() returns false → scheduler never starts.
  • Existing install: preference key syncEnabled is absent in SharedPreferences → same false default applies.
  • setSyncEnabled(true) → scheduler starts on next BackgroundService.onStartCommand().

Android sync silently no-ops for most users because the app-scoped data
directory (Android/data/.../files/sync/) is inaccessible to Syncthing and
other sync clients since Android 11 scoped storage restrictions. Running
the 15-minute scheduler in this state wastes battery and risks OOM crashes
(see aw-server-rust#630).

Gate SyncScheduler.start() and SyncAlarmReceiver behind AWPreferences.isSyncEnabled()
(default: false). setSyncEnabled(true) is the hook for a future settings toggle
or Storage Access Framework setup flow.

Closes #1357 (partial — disables auto-sync; SAF setup flow is follow-up)
@greptile-apps

greptile-apps Bot commented Jul 12, 2026

Copy link
Copy Markdown

Greptile Summary

This PR disables Android sync by default and adds cleanup for stale sync alarms. The main changes are:

  • A new syncEnabled preference defaulting to off.
  • Background sync startup gated behind that preference.
  • Alarm-triggered sync skipped when the preference is off.
  • Stale AlarmManager sync alarms cancelled from the disabled receiver path.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
mobile/src/main/java/net/activitywatch/android/AWPreferences.kt Adds the default-off sync preference and setter.
mobile/src/main/java/net/activitywatch/android/BackgroundService.kt Starts the sync scheduler only when sync is enabled.
mobile/src/main/java/net/activitywatch/android/SyncAlarmReceiver.kt Skips scheduled sync while disabled and cancels stale alarms.
mobile/src/main/java/net/activitywatch/android/SyncScheduler.kt Adds a helper for cancelling the scheduler alarm PendingIntent.

Reviews (2): Last reviewed commit: "fix(sync): cancel stale AlarmManager ala..." | Re-trigger Greptile

Comment on lines +19 to +22
if (!AWPreferences(context).isSyncEnabled()) {
Log.i(TAG, "Sync is disabled; skipping alarm-triggered sync")
return
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Disabled Sync Leaves Alarm Armed

When an older build or a previous enabled session has already registered the repeating wakeup alarm, this branch skips the sync work but leaves that alarm active. Because SyncScheduler.stop() does not cancel the AlarmManager fallback, the app can keep waking every 15 minutes and logging this path even though sync is now disabled.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1d8d23a: added SyncScheduler.cancelAlarm(context) to the disabled-sync branch in SyncAlarmReceiver. When the alarm fires but sync is off, we now cancel the AlarmManager wakeup via FLAG_NO_CREATE PendingIntent retrieval + am.cancel(), so no further 15-minute wakeups occur. The cancel logic lives in a new SyncScheduler.companion object to keep the PendingIntent construction in one place.

When the alarm fires but sync is disabled (e.g. after a downgrade or before
the user enables the pref), cancel the alarm via SyncScheduler.cancelAlarm()
instead of just skipping. Addresses Greptile P1: disabled sync left the
wakeup alarm armed, causing 15-minute wake-ups that silently no-op.
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

@greptileai review

@ErikBjare ErikBjare merged commit 812c25c into ActivityWatch:master Jul 12, 2026
8 checks passed
ErikBjare pushed a commit that referenced this pull request Jul 13, 2026
… Play Store upload) (#190)

* fix: remove FOREGROUND_SERVICE_DATA_SYNC permission + service type

Sync is disabled by default (#184) and not yet demonstrable, so
the Google Play Console FOREGROUND_SERVICE_DATA_SYNC declaration
(which requires a video demo) was blocking the v0.14.0 Play Store upload.

Remove:
- FOREGROUND_SERVICE_DATA_SYNC uses-permission from AndroidManifest.xml
- android:foregroundServiceType="dataSync" from BackgroundService declaration
- ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC from startForeground() call

The base FOREGROUND_SERVICE permission is preserved for the monitoring
service. Re-add the data_sync type and permission when sync is actually
implemented and demonstrable.

Closes #189 (item 1)
Unblocks v0.14.0 Play Store production upload

* fix: use FOREGROUND_SERVICE_TYPE_SPECIAL_USE on Android 14+

On Android 14+ (API 34+) with targetSdk 35, startForeground() requires a
valid foreground service type declared in the manifest. Passing type 0 with
no foregroundServiceType attribute throws MissingForegroundServiceTypeException
at runtime, crashing BackgroundService on any Android 14+ device.

FOREGROUND_SERVICE_TYPE_SPECIAL_USE is the correct type for ActivityWatch's
local HTTP server use case — it doesn't fit the 2024/2025 taxonomy (camera,
microphone, location, health, etc.) and Google added specialUse specifically
for this situation. The Play Store form for specialUse requires a text
description (no demo video), so this unblocks the v0.14.0 upload.

Changes:
- Add FOREGROUND_SERVICE_SPECIAL_USE permission
- Declare foregroundServiceType='specialUse' on BackgroundService
- Pass ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE on API 34+, 0 otherwise

* fix: add PROPERTY_SPECIAL_USE_FGS_SUBTYPE manifest property

Greptile flagged that specialUse without a subtype property can leave
Play review without a machine-readable rationale. The property documents
AW's use case (local on-device HTTP server for the web UI) directly in
the manifest, aligned with the Play Console declaration form.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

aw-sync: Android sync UX needs a complete pass (setup flow, default-off, storage access)

2 participants