diff --git a/mobile/src/main/java/net/activitywatch/android/AWPreferences.kt b/mobile/src/main/java/net/activitywatch/android/AWPreferences.kt index 227faaed..f4a5c711 100644 --- a/mobile/src/main/java/net/activitywatch/android/AWPreferences.kt +++ b/mobile/src/main/java/net/activitywatch/android/AWPreferences.kt @@ -36,4 +36,14 @@ class AWPreferences(context: Context) { fun setHostnameMigrated() { sharedPreferences.edit().putBoolean("hasMigratedHostname", true).apply() } + + // Sync is off by default; user must explicitly enable it once a sync directory + // is configured (e.g. via Storage Access Framework). + fun isSyncEnabled(): Boolean { + return sharedPreferences.getBoolean("syncEnabled", false) + } + + fun setSyncEnabled(enabled: Boolean) { + sharedPreferences.edit().putBoolean("syncEnabled", enabled).apply() + } } diff --git a/mobile/src/main/java/net/activitywatch/android/BackgroundService.kt b/mobile/src/main/java/net/activitywatch/android/BackgroundService.kt index c0588e7c..df29cee8 100644 --- a/mobile/src/main/java/net/activitywatch/android/BackgroundService.kt +++ b/mobile/src/main/java/net/activitywatch/android/BackgroundService.kt @@ -85,8 +85,14 @@ class BackgroundService : Service() { } } - // Start the sync scheduler - syncScheduler.start() + // Start the sync scheduler only when the user has enabled sync. + // Default is off — the sync directory is not accessible to other apps + // (Android scoped storage), so auto-sync would silently no-op for most users. + if (prefs.isSyncEnabled()) { + syncScheduler.start() + } else { + Log.i(TAG, "Sync is disabled (default). Enable it in settings to start syncing.") + } // Schedule event parsing scheduleEventParsing() diff --git a/mobile/src/main/java/net/activitywatch/android/SyncAlarmReceiver.kt b/mobile/src/main/java/net/activitywatch/android/SyncAlarmReceiver.kt index f74cfbf9..2db62ac1 100644 --- a/mobile/src/main/java/net/activitywatch/android/SyncAlarmReceiver.kt +++ b/mobile/src/main/java/net/activitywatch/android/SyncAlarmReceiver.kt @@ -16,6 +16,11 @@ class SyncAlarmReceiver : BroadcastReceiver() { when (intent.action) { "net.activitywatch.android.SYNC_ALARM" -> { + if (!AWPreferences(context).isSyncEnabled()) { + Log.i(TAG, "Sync is disabled; cancelling stale alarm") + SyncScheduler.cancelAlarm(context) + return + } Log.i(TAG, "Performing scheduled sync...") val pendingResult = goAsync() // Create SyncInterface and perform sync on IO dispatcher to avoid diff --git a/mobile/src/main/java/net/activitywatch/android/SyncScheduler.kt b/mobile/src/main/java/net/activitywatch/android/SyncScheduler.kt index d43c6db2..e396203b 100644 --- a/mobile/src/main/java/net/activitywatch/android/SyncScheduler.kt +++ b/mobile/src/main/java/net/activitywatch/android/SyncScheduler.kt @@ -105,4 +105,24 @@ class SyncScheduler(private val context: Context) { } } } + + companion object { + // Called by SyncAlarmReceiver when sync is disabled, to cancel a stale alarm left + // from a previous enable session or an older build that registered it before the pref existed. + fun cancelAlarm(context: Context) { + val am = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + val intent = Intent(ACTION_SYNC_ALARM).setPackage(context.packageName) + val pi = PendingIntent.getBroadcast( + context, + 0, + intent, + PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE + ) + if (pi != null) { + am.cancel(pi) + pi.cancel() + Log.i(TAG, "Cancelled stale AlarmManager sync alarm (sync disabled)") + } + } + } }