Loading app/src/main/kotlin/at/bitfire/davdroid/Constants.kt +2 −0 Original line number Diff line number Diff line Loading @@ -34,4 +34,6 @@ object Constants { const val E_SYNC_URL = "e.email" const val MURENA_DAV_URL = "https://murena.io/remote.php/dav" const val HTTP_STATUS_CODE_TOO_MANY_REQUESTS = 429 } app/src/main/kotlin/at/bitfire/davdroid/db/Credentials.kt +2 −1 Original line number Diff line number Diff line Loading @@ -13,7 +13,8 @@ data class Credentials( val authState: AuthState? = null, val certificateAlias: String? = null, val serverUri: URI? = null, val clientSecret: String? = null val clientSecret: String? = null, var passwordNeedsUpdate: Boolean = false ) { override fun toString(): String { Loading app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt +10 −1 Original line number Diff line number Diff line Loading @@ -73,6 +73,7 @@ class AccountSettings( const val KEY_AUTH_STATE = "auth_state" const val KEY_CLIENT_SECRET = "client_secret" const val KEY_CERTIFICATE_ALIAS = "certificate_alias" const val KEY_PASSWORD_NEEDS_UPDATE = "password_needs_update" const val KEY_WIFI_ONLY = "wifi_only" // sync on WiFi only (default: false) const val KEY_WIFI_ONLY_SSIDS = "wifi_only_ssids" // restrict sync to specific WiFi SSIDs Loading Loading @@ -156,6 +157,9 @@ class AccountSettings( if (credentials.clientSecret != null) { bundle.putString(KEY_CLIENT_SECRET, credentials.clientSecret) } bundle.putString(KEY_PASSWORD_NEEDS_UPDATE, credentials.passwordNeedsUpdate.toString()) } if (!cookies.isNullOrEmpty()) { Loading Loading @@ -270,7 +274,9 @@ class AccountSettings( accountManager.getUserData(account, KEY_USERNAME), accountManager.getPassword(account), null, accountManager.getUserData(account, KEY_CERTIFICATE_ALIAS) accountManager.getUserData(account, KEY_CERTIFICATE_ALIAS), passwordNeedsUpdate = accountManager.getUserData( account, KEY_PASSWORD_NEEDS_UPDATE).toBoolean() ) } else { Credentials( Loading @@ -287,6 +293,9 @@ class AccountSettings( // Basic/Digest auth accountManager.setAndVerifyUserData(account, KEY_USERNAME, credentials.userName) accountManager.setPassword(account, credentials.password) accountManager.setAndVerifyUserData(account, KEY_PASSWORD_NEEDS_UPDATE, credentials.passwordNeedsUpdate.toString() ) // client certificate accountManager.setAndVerifyUserData(account, KEY_CERTIFICATE_ALIAS, credentials.certificateAlias) Loading app/src/main/kotlin/at/bitfire/davdroid/syncadapter/SyncManager.kt +17 −4 Original line number Diff line number Diff line Loading @@ -179,8 +179,14 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L * Call performSync with default retry values */ fun performSync() { val authState = accountSettings.credentials().authState val credentials = accountSettings.credentials() if (credentials.passwordNeedsUpdate) { val exception = UnauthorizedException(context.getString(R.string.sync_error_authentication_failed)) notifyException(exception, null, null) return } val authState = credentials.authState if (authState == null || !authState.needsTokenRefresh) { performSync(DEFAULT_RETRY_AFTER, DEFAULT_SECOND_RETRY_AFTER, DEFAULT_MAX_RETRY_TIME) return Loading Loading @@ -222,8 +228,7 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L * @param secondRetryAfter optional param, in seconds. Used to calculate fibonnacci sequence for rety on unhandled exception * @param maxRetryTime optional param, in seconds. On unhandled exception, max time the method should retry. */ fun performSync(retryAfter: Int, secondRetryAfter: Int, maxRetryTime: Int) { private fun performSync(retryAfter: Int, secondRetryAfter: Int, maxRetryTime: Int) { // dismiss previous error notifications notificationManager.cancel(notificationTag, NotificationUtils.NOTIFY_SYNC_ERROR) Loading Loading @@ -379,6 +384,11 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L is UnauthorizedException -> { Logger.log.log(Level.WARNING, "Got 401 Unauthorized", e) val credentials = accountSettings.credentials() if (credentials.authState == null) { credentials.passwordNeedsUpdate = true accountSettings.credentials(credentials) } notifyException(e, local, remote) return@unwrapExceptions } Loading Loading @@ -407,6 +417,10 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L if (retryAfter > 0 && secondRetryAfter > 0 && retryAfter <= maxRetryTime) { try { Logger.log.severe("Faced unhandled exception $e, Will retry sync") if (e is HttpException && e.code == Constants.HTTP_STATUS_CODE_TOO_MANY_REQUESTS) { Logger.log.info("HTTP 429 Too Many Requests: Retry sync cancelled") return false } Logger.log.info("Retry sync after $retryAfter seconds") Thread.sleep(retryAfter * 1000L) performSync(secondRetryAfter, retryAfter + secondRetryAfter, maxRetryTime) Loading Loading @@ -940,7 +954,6 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L .setCategory(NotificationCompat.CATEGORY_ERROR) viewItemAction?.let { builder.addAction(it) } notificationManager.notifyIfPossible(tag, NotificationUtils.NOTIFY_SYNC_ERROR, builder.build()) } Loading app/src/main/kotlin/at/bitfire/davdroid/ui/account/SettingsActivity.kt +14 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ import android.accounts.Account import android.accounts.AccountManager import android.annotation.SuppressLint import android.app.Application import android.app.NotificationManager import android.content.Intent import android.os.Build import android.os.Bundle Loading Loading @@ -39,6 +40,7 @@ import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.TaskUtils import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.settings.SettingsManager import at.bitfire.davdroid.syncadapter.SyncUtils import at.bitfire.davdroid.syncadapter.SyncWorker import at.bitfire.davdroid.syncadapter.Syncer import at.bitfire.davdroid.ui.UiUtils Loading Loading @@ -133,6 +135,13 @@ class SettingsActivity: AppCompatActivity() { return true } private fun cancelNotifications() { context?.getSystemService(NotificationManager::class.java)?.let { notificationManager -> notificationManager.cancelAll() Logger.log.info("All notifications canceled successfully") } ?: Logger.log.warning("Failed to cancel notifications: Context is null") } private fun initSettings() { // preference group: sync findPreference<ListPreference>(getString(R.string.settings_sync_interval_contacts_key))!!.let { Loading Loading @@ -292,7 +301,12 @@ class SettingsActivity: AppCompatActivity() { certificateAlias = credentials.certificateAlias, authState = credentials.authState, clientSecret = credentials.clientSecret, passwordNeedsUpdate = false, serverUri = credentials.serverUri)) // Cancel all notifications on password change. cancelNotifications() // Sync accounts once password is changed. context?.let { SyncUtils.syncAllAccounts(it) } false } } else Loading Loading
app/src/main/kotlin/at/bitfire/davdroid/Constants.kt +2 −0 Original line number Diff line number Diff line Loading @@ -34,4 +34,6 @@ object Constants { const val E_SYNC_URL = "e.email" const val MURENA_DAV_URL = "https://murena.io/remote.php/dav" const val HTTP_STATUS_CODE_TOO_MANY_REQUESTS = 429 }
app/src/main/kotlin/at/bitfire/davdroid/db/Credentials.kt +2 −1 Original line number Diff line number Diff line Loading @@ -13,7 +13,8 @@ data class Credentials( val authState: AuthState? = null, val certificateAlias: String? = null, val serverUri: URI? = null, val clientSecret: String? = null val clientSecret: String? = null, var passwordNeedsUpdate: Boolean = false ) { override fun toString(): String { Loading
app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt +10 −1 Original line number Diff line number Diff line Loading @@ -73,6 +73,7 @@ class AccountSettings( const val KEY_AUTH_STATE = "auth_state" const val KEY_CLIENT_SECRET = "client_secret" const val KEY_CERTIFICATE_ALIAS = "certificate_alias" const val KEY_PASSWORD_NEEDS_UPDATE = "password_needs_update" const val KEY_WIFI_ONLY = "wifi_only" // sync on WiFi only (default: false) const val KEY_WIFI_ONLY_SSIDS = "wifi_only_ssids" // restrict sync to specific WiFi SSIDs Loading Loading @@ -156,6 +157,9 @@ class AccountSettings( if (credentials.clientSecret != null) { bundle.putString(KEY_CLIENT_SECRET, credentials.clientSecret) } bundle.putString(KEY_PASSWORD_NEEDS_UPDATE, credentials.passwordNeedsUpdate.toString()) } if (!cookies.isNullOrEmpty()) { Loading Loading @@ -270,7 +274,9 @@ class AccountSettings( accountManager.getUserData(account, KEY_USERNAME), accountManager.getPassword(account), null, accountManager.getUserData(account, KEY_CERTIFICATE_ALIAS) accountManager.getUserData(account, KEY_CERTIFICATE_ALIAS), passwordNeedsUpdate = accountManager.getUserData( account, KEY_PASSWORD_NEEDS_UPDATE).toBoolean() ) } else { Credentials( Loading @@ -287,6 +293,9 @@ class AccountSettings( // Basic/Digest auth accountManager.setAndVerifyUserData(account, KEY_USERNAME, credentials.userName) accountManager.setPassword(account, credentials.password) accountManager.setAndVerifyUserData(account, KEY_PASSWORD_NEEDS_UPDATE, credentials.passwordNeedsUpdate.toString() ) // client certificate accountManager.setAndVerifyUserData(account, KEY_CERTIFICATE_ALIAS, credentials.certificateAlias) Loading
app/src/main/kotlin/at/bitfire/davdroid/syncadapter/SyncManager.kt +17 −4 Original line number Diff line number Diff line Loading @@ -179,8 +179,14 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L * Call performSync with default retry values */ fun performSync() { val authState = accountSettings.credentials().authState val credentials = accountSettings.credentials() if (credentials.passwordNeedsUpdate) { val exception = UnauthorizedException(context.getString(R.string.sync_error_authentication_failed)) notifyException(exception, null, null) return } val authState = credentials.authState if (authState == null || !authState.needsTokenRefresh) { performSync(DEFAULT_RETRY_AFTER, DEFAULT_SECOND_RETRY_AFTER, DEFAULT_MAX_RETRY_TIME) return Loading Loading @@ -222,8 +228,7 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L * @param secondRetryAfter optional param, in seconds. Used to calculate fibonnacci sequence for rety on unhandled exception * @param maxRetryTime optional param, in seconds. On unhandled exception, max time the method should retry. */ fun performSync(retryAfter: Int, secondRetryAfter: Int, maxRetryTime: Int) { private fun performSync(retryAfter: Int, secondRetryAfter: Int, maxRetryTime: Int) { // dismiss previous error notifications notificationManager.cancel(notificationTag, NotificationUtils.NOTIFY_SYNC_ERROR) Loading Loading @@ -379,6 +384,11 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L is UnauthorizedException -> { Logger.log.log(Level.WARNING, "Got 401 Unauthorized", e) val credentials = accountSettings.credentials() if (credentials.authState == null) { credentials.passwordNeedsUpdate = true accountSettings.credentials(credentials) } notifyException(e, local, remote) return@unwrapExceptions } Loading Loading @@ -407,6 +417,10 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L if (retryAfter > 0 && secondRetryAfter > 0 && retryAfter <= maxRetryTime) { try { Logger.log.severe("Faced unhandled exception $e, Will retry sync") if (e is HttpException && e.code == Constants.HTTP_STATUS_CODE_TOO_MANY_REQUESTS) { Logger.log.info("HTTP 429 Too Many Requests: Retry sync cancelled") return false } Logger.log.info("Retry sync after $retryAfter seconds") Thread.sleep(retryAfter * 1000L) performSync(secondRetryAfter, retryAfter + secondRetryAfter, maxRetryTime) Loading Loading @@ -940,7 +954,6 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L .setCategory(NotificationCompat.CATEGORY_ERROR) viewItemAction?.let { builder.addAction(it) } notificationManager.notifyIfPossible(tag, NotificationUtils.NOTIFY_SYNC_ERROR, builder.build()) } Loading
app/src/main/kotlin/at/bitfire/davdroid/ui/account/SettingsActivity.kt +14 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ import android.accounts.Account import android.accounts.AccountManager import android.annotation.SuppressLint import android.app.Application import android.app.NotificationManager import android.content.Intent import android.os.Build import android.os.Bundle Loading Loading @@ -39,6 +40,7 @@ import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.TaskUtils import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.settings.SettingsManager import at.bitfire.davdroid.syncadapter.SyncUtils import at.bitfire.davdroid.syncadapter.SyncWorker import at.bitfire.davdroid.syncadapter.Syncer import at.bitfire.davdroid.ui.UiUtils Loading Loading @@ -133,6 +135,13 @@ class SettingsActivity: AppCompatActivity() { return true } private fun cancelNotifications() { context?.getSystemService(NotificationManager::class.java)?.let { notificationManager -> notificationManager.cancelAll() Logger.log.info("All notifications canceled successfully") } ?: Logger.log.warning("Failed to cancel notifications: Context is null") } private fun initSettings() { // preference group: sync findPreference<ListPreference>(getString(R.string.settings_sync_interval_contacts_key))!!.let { Loading Loading @@ -292,7 +301,12 @@ class SettingsActivity: AppCompatActivity() { certificateAlias = credentials.certificateAlias, authState = credentials.authState, clientSecret = credentials.clientSecret, passwordNeedsUpdate = false, serverUri = credentials.serverUri)) // Cancel all notifications on password change. cancelNotifications() // Sync accounts once password is changed. context?.let { SyncUtils.syncAllAccounts(it) } false } } else Loading