From 38799b3708691d00c551d6cbc8c906fdc96c35ce Mon Sep 17 00:00:00 2001 From: Andrew Gunnerson Date: Fri, 5 Jun 2026 20:09:02 -0400 Subject: [PATCH] Add setting to completely disable auto mode When disabled: * All settings for auto mode disappear (although settings will not be lost if auto mode is later allowed again). * The auto/manual mode button is removed from the notification, leaving only start/stop. * The quick settings tile only cycles between manually started and manually stopped. Issue: #150 Signed-off-by: Andrew Gunnerson --- .../com/chiller3/basicsync/Preferences.kt | 5 + .../basicsync/settings/SettingsScreen.kt | 142 +++++++++++------- .../basicsync/syncthing/SyncthingService.kt | 14 +- .../syncthing/SyncthingTileService.kt | 7 +- app/src/main/res/values/strings.xml | 4 + 5 files changed, 113 insertions(+), 59 deletions(-) diff --git a/app/src/main/java/com/chiller3/basicsync/Preferences.kt b/app/src/main/java/com/chiller3/basicsync/Preferences.kt index 9a0dd9e..8667383 100644 --- a/app/src/main/java/com/chiller3/basicsync/Preferences.kt +++ b/app/src/main/java/com/chiller3/basicsync/Preferences.kt @@ -21,6 +21,7 @@ class Preferences(context: Context) { const val PREF_RESPECT_AUTO_SYNC_DATA = "respect_auto_sync_data" const val PREF_KEEP_ALIVE = "keep_alive" const val PREF_REMOTE_CONTROL = "remote_control" + const val PREF_ALLOW_AUTO_MODE = "allow_auto_mode" const val PREF_SHOW_EXIT = "show_exit" const val PREF_REQUIRE_UNMETERED_NETWORK = "require_unmetered_network" const val PREF_NETWORK_ALLOW_WIFI = "network_allow_wifi" @@ -76,6 +77,10 @@ class Preferences(context: Context) { get() = prefs.getBoolean(PREF_REMOTE_CONTROL, false) set(enabled) = prefs.edit { putBoolean(PREF_REMOTE_CONTROL, enabled) } + var allowAutoMode: Boolean + get() = prefs.getBoolean(PREF_ALLOW_AUTO_MODE, true) + set(enabled) = prefs.edit { putBoolean(PREF_ALLOW_AUTO_MODE, enabled) } + var showExit: Boolean get() = prefs.getBoolean(PREF_SHOW_EXIT, false) set(enabled) = prefs.edit { putBoolean(PREF_SHOW_EXIT, enabled) } diff --git a/app/src/main/java/com/chiller3/basicsync/settings/SettingsScreen.kt b/app/src/main/java/com/chiller3/basicsync/settings/SettingsScreen.kt index 703b0bb..d31e6b8 100644 --- a/app/src/main/java/com/chiller3/basicsync/settings/SettingsScreen.kt +++ b/app/src/main/java/com/chiller3/basicsync/settings/SettingsScreen.kt @@ -104,6 +104,7 @@ fun SettingsScreen( val respectAutoSyncData = remember(reloadPrefs) { prefs.respectAutoSyncData } val keepAlive = remember(reloadPrefs) { prefs.keepAlive } val remoteControl = remember(reloadPrefs) { prefs.remoteControl } + val allowAutoMode = remember(reloadPrefs) { prefs.allowAutoMode } val showExit = remember(reloadPrefs) { prefs.showExit } val isDebugMode = remember(reloadPrefs) { prefs.isDebugMode } @@ -295,6 +296,7 @@ fun SettingsScreen( respectAutoSyncData = respectAutoSyncData, keepAlive = keepAlive, remoteControl = remoteControl, + allowAutoMode = allowAutoMode, showExit = showExit, isDebugMode = isDebugMode, onInhibitBatteryOptGrant = { @@ -386,6 +388,18 @@ fun SettingsScreen( prefs.remoteControl = enabled reloadPrefs++ }, + onAllowAutoModeChange = { enabled -> + prefs.allowAutoMode = enabled + reloadPrefs++ + + val action = if (enabled) { + SyncthingService.ACTION_RENOTIFY + } else { + SyncthingService.ACTION_MANUAL_MODE + } + + SyncthingService.start(context, action) + }, onShowExitChange = { enabled -> prefs.showExit = enabled reloadPrefs++ @@ -491,6 +505,7 @@ private fun SettingsContent( respectAutoSyncData: Boolean, keepAlive: Boolean, remoteControl: Boolean, + allowAutoMode: Boolean, showExit: Boolean, isDebugMode: Boolean, onInhibitBatteryOptGrant: () -> Unit, @@ -511,6 +526,7 @@ private fun SettingsContent( onSyncScheduleSettingsOpen: () -> Unit, onKeepAliveChange: (Boolean) -> Unit, onRemoteControlChange: (Boolean) -> Unit, + onAllowAutoModeChange: (Boolean) -> Unit, onShowExitChange: (Boolean) -> Unit, onDebugModeChange: (Boolean) -> Unit, onSourceRepoOpen: () -> Unit, @@ -665,73 +681,75 @@ private fun SettingsContent( ) } - item(key = "auto_mode") { - SwitchPreference( - checked = !isManualMode, - onCheckedChange = { onManualModeChange(!it) }, - shapes = BetterSegmentedShapes.middle(), - title = { Text(text = stringResource(R.string.pref_auto_mode_name)) }, - summary = { Text(text = stringResource(R.string.pref_auto_mode_desc)) }, - modifier = Modifier.animateItem(), - ) - } - - item(key = "network_conditions") { - Preference( - onClick = onNetworkConditionsOpen, - shapes = BetterSegmentedShapes.middle(), - title = { Text(text = stringResource(R.string.pref_network_conditions_name)) }, - summary = { Text(text = stringResource(R.string.pref_network_conditions_desc)) }, - modifier = Modifier.animateItem(), - ) - } - - if (hasBattery) { - item(key = "run_on_battery") { - val summary = stringResource(R.string.pref_run_on_battery_desc, minBatteryLevel) + if (allowAutoMode) { + item(key = "auto_mode") { + SwitchPreference( + checked = !isManualMode, + onCheckedChange = { onManualModeChange(!it) }, + shapes = BetterSegmentedShapes.middle(), + title = { Text(text = stringResource(R.string.pref_auto_mode_name)) }, + summary = { Text(text = stringResource(R.string.pref_auto_mode_desc)) }, + modifier = Modifier.animateItem(), + ) + } - SplitSwitchPreference( - onClick = { showMinBatteryLevelDialog = true }, - checked = runOnBattery, - onCheckedChange = onRunOnBatteryChange, + item(key = "network_conditions") { + Preference( + onClick = onNetworkConditionsOpen, shapes = BetterSegmentedShapes.middle(), - title = { Text(text = stringResource(R.string.pref_run_on_battery_name)) }, - summary = { Text(text = summary) }, + title = { Text(text = stringResource(R.string.pref_network_conditions_name)) }, + summary = { Text(text = stringResource(R.string.pref_network_conditions_desc)) }, modifier = Modifier.animateItem(), ) } - item(key = "respect_battery_saver") { + if (hasBattery) { + item(key = "run_on_battery") { + val summary = stringResource(R.string.pref_run_on_battery_desc, minBatteryLevel) + + SplitSwitchPreference( + onClick = { showMinBatteryLevelDialog = true }, + checked = runOnBattery, + onCheckedChange = onRunOnBatteryChange, + shapes = BetterSegmentedShapes.middle(), + title = { Text(text = stringResource(R.string.pref_run_on_battery_name)) }, + summary = { Text(text = summary) }, + modifier = Modifier.animateItem(), + ) + } + + item(key = "respect_battery_saver") { + SwitchPreference( + checked = respectBatterySaver, + onCheckedChange = onRespectBatterySaverChange, + shapes = BetterSegmentedShapes.middle(), + title = { Text(text = stringResource(R.string.pref_respect_battery_saver_name)) }, + summary = { Text(text = stringResource(R.string.pref_respect_battery_saver_desc)) }, + modifier = Modifier.animateItem(), + ) + } + } + + item(key = "respect_auto_sync_data") { SwitchPreference( - checked = respectBatterySaver, - onCheckedChange = onRespectBatterySaverChange, + checked = respectAutoSyncData, + onCheckedChange = onRespectAutoSyncDataChange, shapes = BetterSegmentedShapes.middle(), - title = { Text(text = stringResource(R.string.pref_respect_battery_saver_name)) }, - summary = { Text(text = stringResource(R.string.pref_respect_battery_saver_desc)) }, + title = { Text(text = stringResource(R.string.pref_respect_auto_sync_data_name)) }, + summary = { Text(text = stringResource(R.string.pref_respect_auto_sync_data_desc)) }, modifier = Modifier.animateItem(), ) } - } - item(key = "respect_auto_sync_data") { - SwitchPreference( - checked = respectAutoSyncData, - onCheckedChange = onRespectAutoSyncDataChange, - shapes = BetterSegmentedShapes.middle(), - title = { Text(text = stringResource(R.string.pref_respect_auto_sync_data_name)) }, - summary = { Text(text = stringResource(R.string.pref_respect_auto_sync_data_desc)) }, - modifier = Modifier.animateItem(), - ) - } - - item(key = "sync_schedule_settings") { - Preference( - onClick = onSyncScheduleSettingsOpen, - shapes = BetterSegmentedShapes.middle(), - title = { Text(text = stringResource(R.string.pref_sync_schedule_settings_name)) }, - summary = { Text(text = stringResource(R.string.pref_sync_schedule_settings_desc)) }, - modifier = Modifier.animateItem(), - ) + item(key = "sync_schedule_settings") { + Preference( + onClick = onSyncScheduleSettingsOpen, + shapes = BetterSegmentedShapes.middle(), + title = { Text(text = stringResource(R.string.pref_sync_schedule_settings_name)) }, + summary = { Text(text = stringResource(R.string.pref_sync_schedule_settings_desc)) }, + modifier = Modifier.animateItem(), + ) + } } item(key = "keep_alive") { @@ -763,6 +781,17 @@ private fun SettingsContent( ) } + item(key = "allow_auto_mode") { + SwitchPreference( + checked = allowAutoMode, + onCheckedChange = onAllowAutoModeChange, + shapes = BetterSegmentedShapes.middle(), + title = { Text(text = stringResource(R.string.pref_allow_auto_mode_name)) }, + summary = { Text(text = stringResource(R.string.pref_allow_auto_mode_desc)) }, + modifier = Modifier.animateItem(), + ) + } + item(key = "show_exit") { SwitchPreference( checked = showExit, @@ -895,6 +924,7 @@ private fun PreviewSettingsScreen() { isStarted = true, isResumed = true, manualMode = false, + allowAutoMode = true, preRunAction = null, showExit = false, ) @@ -919,6 +949,7 @@ private fun PreviewSettingsScreen() { respectAutoSyncData = true, keepAlive = false, remoteControl = false, + allowAutoMode = true, showExit = false, isDebugMode = true, onInhibitBatteryOptGrant = {}, @@ -939,6 +970,7 @@ private fun PreviewSettingsScreen() { onSyncScheduleSettingsOpen = {}, onKeepAliveChange = {}, onRemoteControlChange = {}, + onAllowAutoModeChange = {}, onShowExitChange = {}, onDebugModeChange = {}, onSourceRepoOpen = {}, diff --git a/app/src/main/java/com/chiller3/basicsync/syncthing/SyncthingService.kt b/app/src/main/java/com/chiller3/basicsync/syncthing/SyncthingService.kt index 7d69950..affd9b1 100644 --- a/app/src/main/java/com/chiller3/basicsync/syncthing/SyncthingService.kt +++ b/app/src/main/java/com/chiller3/basicsync/syncthing/SyncthingService.kt @@ -82,6 +82,7 @@ class SyncthingService : Service(), SyncthingStatusReceiver, DeviceStateListener private val isStarted: Boolean, private val isResumed: Boolean, private val manualMode: Boolean, + private val allowAutoMode: Boolean, private val preRunAction: PreRunAction?, private val showExit: Boolean, ) { @@ -128,7 +129,9 @@ class SyncthingService : Service(), SyncthingStatusReceiver, DeviceStateListener get() = ArrayList().apply { if (preRunAction == null) { if (manualMode) { - add(ACTION_AUTO_MODE) + if (allowAutoMode) { + add(ACTION_AUTO_MODE) + } if (shouldResume) { add(ACTION_STOP) @@ -317,7 +320,13 @@ class SyncthingService : Service(), SyncthingStatusReceiver, DeviceStateListener var forceShowNotification = false when (intent?.action) { - ACTION_AUTO_MODE -> prefs.isManualMode = false + ACTION_AUTO_MODE -> { + if (prefs.allowAutoMode) { + prefs.isManualMode = false + } else { + Log.d(TAG, "Ignoring switch to auto mode because auto mode is blocked") + } + } ACTION_MANUAL_MODE -> { // Keep the current state since the user has no way to know what the previously // saved state is anyway. @@ -431,6 +440,7 @@ class SyncthingService : Service(), SyncthingStatusReceiver, DeviceStateListener isStarted = isStarted, isResumed = isResumed, manualMode = prefs.isManualMode, + allowAutoMode = prefs.allowAutoMode, preRunAction = currentPreRunAction, showExit = prefs.showExit, ) diff --git a/app/src/main/java/com/chiller3/basicsync/syncthing/SyncthingTileService.kt b/app/src/main/java/com/chiller3/basicsync/syncthing/SyncthingTileService.kt index a35b0b1..57b8e17 100644 --- a/app/src/main/java/com/chiller3/basicsync/syncthing/SyncthingTileService.kt +++ b/app/src/main/java/com/chiller3/basicsync/syncthing/SyncthingTileService.kt @@ -47,9 +47,12 @@ class SyncthingTileService : TileService(), SharedPreferences.OnSharedPreference } else if (prefs.manualShouldRun) { // Manually started -> manually stopped. SyncthingService.ACTION_STOP - } else { - // Manually stopped -> auto mode. + } else if (prefs.allowAutoMode) { + // Manually stopped -> auto mode (when auto mode allowed). SyncthingService.ACTION_AUTO_MODE + } else { + // Manually stopped -> manually started (when auto mode blocked). + SyncthingService.ACTION_START } startForegroundService(SyncthingService.createIntent(this, action)) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 66de1e5..429d538 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -86,6 +86,10 @@ Allow remote control Allow external apps to switch to auto mode or manually start and stop Syncthing. + + Allow auto mode + + When disabled, all auto mode functionality is completely removed from the app, notification, and quick settings tile. Show Exit button