From cac30678c9d6073e8e43e91c9073cf169134c38e Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Tue, 5 Aug 2025 16:03:36 +0600 Subject: [PATCH] fix: preserve sync enabled/disabled state and sync intervals during SSO migration When migrating a non-SSO Murena account to use SSO, the sync settings for eDrive, Mail, Notes, Contacts, Calendar, and Tasks were not fully preserved. The sync intervals for Contacts, Calendar and Tasks were also being reset. To fix this, when an account is migrated to SSO, the following settings are now carried over from the old account to the new one: - Sync enabled/disabled status for eDrive, Mail, Notes, Contacts, Calendar, and Tasks - Sync interval for Contacts, Calendar, and Tasks. - Group method for Contacts. If it's a new account setup, default sync settings will be applied. Additionally, the logic for handling CardDAV and CalDAV services during account setup has been updated to: - For existing accounts being migrated: Update the existing service record in the database with the new authentication state and principal. - For new accounts: Create a new service record. This commit also fixed the issue where the sync settings for Contacts, Calendar and Tasks were reset after a device reboot. --- .../davdroid/settings/AccountSettings.kt | 10 +- .../ui/setup/AccountDetailsFragment.kt | 243 ++++++++++++++---- 2 files changed, 205 insertions(+), 48 deletions(-) diff --git a/app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt b/app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt index f94002a38..9ad42b35a 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt @@ -342,7 +342,7 @@ class AccountSettings( * @throws IllegalArgumentException when [seconds] is not [SYNC_INTERVAL_MANUALLY] but less than 15 min */ @WorkerThread - fun setSyncInterval(authority: String, argSeconds: Long): Boolean { + fun setSyncInterval(authority: String, argSeconds: Long, setAutomaticSync: Boolean = true): Boolean { var seconds = argSeconds if (seconds != SYNC_INTERVAL_MANUALLY && seconds < 60*15) { @@ -369,7 +369,9 @@ class AccountSettings( // Also enable/disable content change triggered syncs (SyncFramework automatic sync). // We could make this a separate user adjustable setting later on. - setSyncOnContentChange(authority, seconds != SYNC_INTERVAL_MANUALLY) + if (setAutomaticSync) { + setSyncOnContentChange(authority, seconds != SYNC_INTERVAL_MANUALLY) + } return true } @@ -626,6 +628,10 @@ class AccountSettings( return } + if (!ContentResolver.getSyncAutomatically(account, authority)) { + return + } + if (authority !in getPeriodicSyncEnableAuthorities()) { return } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt index c003b8409..27883cc30 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt @@ -406,74 +406,225 @@ class AccountDetailsFragment : Fragment() { accountManager.setPassword(account, credentials?.password) } + // Account sync settings for eDrive + var mediaSyncEnabled = true + var appDataSyncEnabled = true + var meteredEdriveSyncEnabled = true + + // Account sync settings for other apps + var notesSyncEnabled = true + var emailSyncEnabled = true + var contactsSyncable = 1 + var calendarSyncable = 1 + var tasksSyncable = 1 + + var contactsGroupMethod = groupMethod + + // Sync interval + val defaultSyncInterval = Constants.DEFAULT_CALENDAR_SYNC_INTERVAL + var contactsSyncInterval = defaultSyncInterval + var calendarSyncInterval = defaultSyncInterval + var tasksSyncInterval = defaultSyncInterval + + var contactsSyncEnabled = true + var calendarSyncEnabled = true + var tasksSyncEnabled = true - ContentResolver.setSyncAutomatically(account, context.getString(R.string.notes_authority), true) - ContentResolver.setSyncAutomatically(account, context.getString(R.string.email_authority), true) - ContentResolver.setSyncAutomatically(account, context.getString(R.string.media_authority), true) - ContentResolver.setSyncAutomatically(account, context.getString(R.string.app_data_authority), true) - ContentResolver.setSyncAutomatically(account, context.getString(R.string.metered_edrive_authority), true) + if (accountToUpdate != null) { + val oldSettings = AccountSettings(context, accountToUpdate) + val addressBookAuthority = context.getString(R.string.address_books_authority) + val taskProvider = TaskUtils.currentProvider(context) + + mediaSyncEnabled = ContentResolver.getSyncAutomatically( + accountToUpdate, + context.getString(R.string.media_authority) + ) + appDataSyncEnabled = ContentResolver.getSyncAutomatically( + accountToUpdate, + context.getString(R.string.app_data_authority) + ) + meteredEdriveSyncEnabled = ContentResolver.getSyncAutomatically( + accountToUpdate, + context.getString(R.string.metered_edrive_authority) + ) + + notesSyncEnabled = ContentResolver.getSyncAutomatically( + accountToUpdate, + context.getString(R.string.notes_authority) + ) + emailSyncEnabled = ContentResolver.getSyncAutomatically( + accountToUpdate, + context.getString(R.string.email_authority) + ) + + // --- Contacts sync logic --- + contactsSyncable = + ContentResolver.getIsSyncable(accountToUpdate, addressBookAuthority) + val oldContactsSyncInterval = oldSettings.getSyncInterval(addressBookAuthority) + contactsSyncEnabled = + ContentResolver.getSyncAutomatically(accountToUpdate, addressBookAuthority) + + contactsSyncInterval = when { + contactsSyncEnabled -> oldContactsSyncInterval ?: defaultSyncInterval + oldContactsSyncInterval != null -> oldContactsSyncInterval + else -> AccountSettings.SYNC_INTERVAL_MANUALLY + } + contactsGroupMethod = oldSettings.getGroupMethod() + + // --- Calendar sync logic --- + calendarSyncable = + ContentResolver.getIsSyncable(accountToUpdate, CalendarContract.AUTHORITY) + val oldCalendarSyncInterval = + oldSettings.getSyncInterval(CalendarContract.AUTHORITY) + calendarSyncEnabled = ContentResolver.getSyncAutomatically( + accountToUpdate, + CalendarContract.AUTHORITY + ) + calendarSyncInterval = when { + calendarSyncEnabled -> oldCalendarSyncInterval ?: defaultSyncInterval + oldCalendarSyncInterval != null -> oldCalendarSyncInterval + else -> AccountSettings.SYNC_INTERVAL_MANUALLY + } + + // --- Tasks sync logic --- + if (taskProvider != null) { + tasksSyncable = + ContentResolver.getIsSyncable(accountToUpdate, taskProvider.authority) + tasksSyncEnabled = + ContentResolver.getSyncAutomatically(accountToUpdate, taskProvider.authority) + val oldTasksSyncInterval = + oldSettings.getSyncInterval(taskProvider.authority) + + tasksSyncInterval = when { + tasksSyncEnabled -> oldTasksSyncInterval ?: defaultSyncInterval + oldTasksSyncInterval != null -> oldTasksSyncInterval + else -> AccountSettings.SYNC_INTERVAL_MANUALLY + } + } + } + + ContentResolver.setSyncAutomatically( + account, + context.getString(R.string.notes_authority), + notesSyncEnabled + ) + ContentResolver.setSyncAutomatically( + account, + context.getString(R.string.email_authority), + emailSyncEnabled + ) + ContentResolver.setSyncAutomatically( + account, + context.getString(R.string.media_authority), + mediaSyncEnabled + ) + ContentResolver.setSyncAutomatically( + account, + context.getString(R.string.app_data_authority), + appDataSyncEnabled + ) + ContentResolver.setSyncAutomatically( + account, + context.getString(R.string.metered_edrive_authority), + meteredEdriveSyncEnabled + ) // add entries for account to service DB Logger.log.log(Level.INFO, "Writing account configuration to database", config) try { val accountSettings = AccountSettings(context, account) - val defaultSyncInterval = Constants.DEFAULT_CALENDAR_SYNC_INTERVAL // Configure CardDAV service - val addrBookAuthority = context.getString(R.string.address_books_authority) + val addressBookAuthority = context.getString(R.string.address_books_authority) if (config.cardDAV != null) { - // insert CardDAV service - val id = insertService( - credentials?.userName ?: "", - credentials?.authState?.jsonSerializeString(), - accountType, - addressBookAccountType, - Service.TYPE_CARDDAV, - config.cardDAV - ) - - // initial CardDAV account settings - accountSettings.setGroupMethod(groupMethod) + if (accountToUpdate != null) { + // Migration: update existing service + val service = db.serviceDao().getByAccountAndType(accountToUpdate.name, Service.TYPE_CARDDAV) + service?.let { + it.authState = credentials?.authState?.jsonSerializeString() + it.principal = config.cardDAV.principal + db.serviceDao().insertOrReplace(it) + RefreshCollectionsWorker.refreshCollections(context, it.id) + } + } else { + // New account: create new service + val id = insertService( + credentials?.userName ?: "", + credentials?.authState?.jsonSerializeString(), + accountType, + addressBookAccountType, + Service.TYPE_CARDDAV, + config.cardDAV + ) + accountSettings.setGroupMethod(contactsGroupMethod) + ContentResolver.setIsSyncable(account, addressBookAuthority, contactsSyncable) + RefreshCollectionsWorker.refreshCollections(context, id) + } - // start CardDAV service detection (refresh collections) - RefreshCollectionsWorker.refreshCollections(context, id) + // set sync settings + ContentResolver.setSyncAutomatically( + account, + addressBookAuthority, + contactsSyncEnabled + ) + accountSettings.setSyncInterval(addressBookAuthority, contactsSyncInterval, false) - // set default sync interval and enable sync regardless of permissions - ContentResolver.setIsSyncable(account, addrBookAuthority, 1) - accountSettings.setSyncInterval(addrBookAuthority, defaultSyncInterval) - } else - ContentResolver.setIsSyncable(account, addrBookAuthority, 0) + } else { + ContentResolver.setIsSyncable(account, addressBookAuthority, 0) + } // Configure CalDAV service if (config.calDAV != null) { - // insert CalDAV service - val id = insertService( - credentials?.userName ?: "", - credentials?.authState?.jsonSerializeString(), - accountType, - addressBookAccountType, - Service.TYPE_CALDAV, - config.calDAV - ) - - // start CalDAV service detection (refresh collections) - RefreshCollectionsWorker.refreshCollections(context, id) + if (accountToUpdate != null) { + // Migration: update existing service + val service = db.serviceDao().getByAccountAndType(accountToUpdate.name, Service.TYPE_CALDAV) + service?.let { + it.authState = credentials?.authState?.jsonSerializeString() + it.principal = config.calDAV.principal + db.serviceDao().insertOrReplace(it) + RefreshCollectionsWorker.refreshCollections(context, it.id) + } + } else { + // New account: create new service + val id = insertService( + credentials?.userName ?: "", + credentials?.authState?.jsonSerializeString(), + accountType, + addressBookAccountType, + Service.TYPE_CALDAV, + config.calDAV + ) + ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, calendarSyncable) + RefreshCollectionsWorker.refreshCollections(context, id) + } - // set default sync interval and enable sync regardless of permissions - ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 1) - accountSettings.setSyncInterval(CalendarContract.AUTHORITY, defaultSyncInterval) + // set sync settings + ContentResolver.setSyncAutomatically( + account, + CalendarContract.AUTHORITY, + calendarSyncEnabled + ) + accountSettings.setSyncInterval(CalendarContract.AUTHORITY, calendarSyncInterval, false) // if task provider present, set task sync interval and enable sync val taskProvider = TaskUtils.currentProvider(context) if (taskProvider != null) { - ContentResolver.setIsSyncable(account, taskProvider.authority, 1) - accountSettings.setSyncInterval(taskProvider.authority, defaultSyncInterval) - // further changes will be handled by TasksWatcher on app start or when tasks app is (un)installed + if (accountToUpdate == null) + ContentResolver.setIsSyncable(account, taskProvider.authority, tasksSyncable) + + ContentResolver.setSyncAutomatically( + account, + taskProvider.authority, + tasksSyncEnabled + ) + accountSettings.setSyncInterval(taskProvider.authority, tasksSyncInterval, false) Logger.log.info("Tasks provider ${taskProvider.authority} found. Tasks sync enabled.") - } else + } else { Logger.log.info("No tasks provider found. Did not enable tasks sync.") - } else + } + } else { ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 0) + } } catch(e: InvalidAccountException) { Logger.log.log(Level.SEVERE, "Couldn't access account settings", e) -- GitLab