Loading app/src/main/kotlin/at/bitfire/davdroid/servicedetection/RefreshCollectionsWorker.kt +12 −4 Original line number Diff line number Diff line Loading @@ -35,10 +35,11 @@ import at.bitfire.davdroid.ui.account.AccountSettingsActivity import dagger.assisted.Assisted import dagger.assisted.AssistedInject import foundation.e.accountmanager.AccountTypes import foundation.e.accountmanager.ui.setup.ReOAuthActivity import foundation.e.accountmanager.utils.AccountHelper import kotlinx.coroutines.flow.map import kotlinx.coroutines.runInterruptible import java.io.IOException import java.io.InterruptedIOException import java.util.logging.Level import java.util.logging.Logger import kotlin.coroutines.cancellation.CancellationException Loading Loading @@ -191,10 +192,17 @@ class RefreshCollectionsWorker @AssistedInject constructor( } catch (e: UnauthorizedException) { logger.log(Level.SEVERE, "Not authorized (anymore)", e) // notify that we need to re-authenticate in the account settings val settingsIntent = Intent(applicationContext, AccountSettingsActivity::class.java) .putExtra(AccountSettingsActivity.EXTRA_ACCOUNT, account) val isOidcAccount = AccountHelper.isOidcAccount(applicationContext, account) val (settingsIntent, notificationMessage) = if (isOidcAccount) { Intent(applicationContext, ReOAuthActivity::class.java) to applicationContext.getString(R.string.sync_error_authentication_oauth) } else { Intent(applicationContext, AccountSettingsActivity::class.java) to applicationContext.getString(R.string.sync_error_authentication_failed) } settingsIntent.putExtra(AccountSettingsActivity.EXTRA_ACCOUNT, account) notifyRefreshError( applicationContext.getString(R.string.sync_error_authentication_failed), notificationMessage, settingsIntent ) return Result.failure() Loading app/src/main/kotlin/at/bitfire/davdroid/sync/SyncManager.kt +6 −1 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.sync.account.InvalidAccountException import at.bitfire.synctools.storage.LocalStorageException import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.accountmanager.utils.AccountHelper import foundation.e.accountmanager.utils.SystemUtils import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.coroutineScope Loading Loading @@ -755,7 +756,11 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L if (isNetworkAvailable) { syncResult.numAuthExceptions++ } message = context.getString(R.string.sync_error_authentication_failed) message = if (AccountHelper.isOidcAccount(context, account)) { context.getString(R.string.sync_error_authentication_oauth) } else { context.getString(R.string.sync_error_authentication_failed) } // persistent session cookie is present. Probably the session is outDated. no need to show the notification if (accountSettings.containsPersistentCookie() && accountSettings.noAuthExceptionDetected()) { Loading app/src/main/kotlin/at/bitfire/davdroid/sync/SyncNotificationManager.kt +7 −1 Original line number Diff line number Diff line Loading @@ -33,6 +33,8 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.accountmanager.ui.setup.ReOAuthActivity import foundation.e.accountmanager.utils.AccountHelper import okhttp3.HttpUrl import org.dmfs.tasks.contract.TaskContract import java.io.IOException Loading Loading @@ -124,7 +126,11 @@ class SyncNotificationManager @AssistedInject constructor( val contentIntent: Intent var viewItemAction: NotificationCompat.Action? = null if (e is UnauthorizedException) { contentIntent = Intent(context, AccountSettingsActivity::class.java) contentIntent = if (AccountHelper.isOidcAccount(context, account)) { Intent(context, ReOAuthActivity::class.java) } else { Intent(context, AccountSettingsActivity::class.java) } contentIntent.putExtra( AccountSettingsActivity.EXTRA_ACCOUNT, account Loading app/src/main/kotlin/foundation/e/accountmanager/ui/setup/ReOAuthActivity.kt 0 → 100644 +95 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 e Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. * */ package foundation.e.accountmanager.ui.setup import android.accounts.Account import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.glance.LocalContext import androidx.hilt.navigation.compose.hiltViewModel import at.bitfire.davdroid.ui.AppTheme import at.bitfire.davdroid.ui.account.AccountSettingsActivity import at.bitfire.davdroid.ui.account.AccountSettingsModel import dagger.hilt.android.AndroidEntryPoint import foundation.e.accountmanager.utils.AccountHelper @AndroidEntryPoint class ReOAuthActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Retrieve the Account from the Intent val account: Account? = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { intent.getParcelableExtra(AccountSettingsActivity.EXTRA_ACCOUNT, Account::class.java) } else { @Suppress("DEPRECATION") intent.getParcelableExtra(AccountSettingsActivity.EXTRA_ACCOUNT) } setContent { AppTheme { if (account != null) { OAuthHandlerScreen( account = account, onFinished = { finish() } ) } else { finish() } } } } } @Composable fun OAuthHandlerScreen( account: Account, onFinished: () -> Unit, ) { val context = LocalContext.current val model = hiltViewModel { factory: AccountSettingsModel.Factory -> factory.create(account) } val authRequestContract = rememberLauncherForActivityResult(model.authorizationContract()) { authResponse -> if (authResponse != null) { model.authenticate(authResponse) // Sync after authenticated AccountHelper.scheduleSyncWithDelay(context) } else { model.authCodeFailed() } onFinished() } // Auto-launch immediately, no UI shown LaunchedEffect(Unit) { val request = model.newAuthorizationRequest() if (request != null) { authRequestContract.launch(request) } else { onFinished() } } } app/src/main/res/values/e_strings.xml +2 −0 Original line number Diff line number Diff line Loading @@ -34,4 +34,6 @@ <string name="privacy_policy_title_nav">"Privacy Policy"</string> <string name="navigation_drawer_open_webcalmanager">Web Calendar Manager</string> <string name="legacy_murena_login">Legacy Murena.io</string> <string name="sync_error_authentication_oauth">Authentication issue. Tap to sign in again</string> </resources> Loading
app/src/main/kotlin/at/bitfire/davdroid/servicedetection/RefreshCollectionsWorker.kt +12 −4 Original line number Diff line number Diff line Loading @@ -35,10 +35,11 @@ import at.bitfire.davdroid.ui.account.AccountSettingsActivity import dagger.assisted.Assisted import dagger.assisted.AssistedInject import foundation.e.accountmanager.AccountTypes import foundation.e.accountmanager.ui.setup.ReOAuthActivity import foundation.e.accountmanager.utils.AccountHelper import kotlinx.coroutines.flow.map import kotlinx.coroutines.runInterruptible import java.io.IOException import java.io.InterruptedIOException import java.util.logging.Level import java.util.logging.Logger import kotlin.coroutines.cancellation.CancellationException Loading Loading @@ -191,10 +192,17 @@ class RefreshCollectionsWorker @AssistedInject constructor( } catch (e: UnauthorizedException) { logger.log(Level.SEVERE, "Not authorized (anymore)", e) // notify that we need to re-authenticate in the account settings val settingsIntent = Intent(applicationContext, AccountSettingsActivity::class.java) .putExtra(AccountSettingsActivity.EXTRA_ACCOUNT, account) val isOidcAccount = AccountHelper.isOidcAccount(applicationContext, account) val (settingsIntent, notificationMessage) = if (isOidcAccount) { Intent(applicationContext, ReOAuthActivity::class.java) to applicationContext.getString(R.string.sync_error_authentication_oauth) } else { Intent(applicationContext, AccountSettingsActivity::class.java) to applicationContext.getString(R.string.sync_error_authentication_failed) } settingsIntent.putExtra(AccountSettingsActivity.EXTRA_ACCOUNT, account) notifyRefreshError( applicationContext.getString(R.string.sync_error_authentication_failed), notificationMessage, settingsIntent ) return Result.failure() Loading
app/src/main/kotlin/at/bitfire/davdroid/sync/SyncManager.kt +6 −1 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.sync.account.InvalidAccountException import at.bitfire.synctools.storage.LocalStorageException import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.accountmanager.utils.AccountHelper import foundation.e.accountmanager.utils.SystemUtils import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.coroutineScope Loading Loading @@ -755,7 +756,11 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L if (isNetworkAvailable) { syncResult.numAuthExceptions++ } message = context.getString(R.string.sync_error_authentication_failed) message = if (AccountHelper.isOidcAccount(context, account)) { context.getString(R.string.sync_error_authentication_oauth) } else { context.getString(R.string.sync_error_authentication_failed) } // persistent session cookie is present. Probably the session is outDated. no need to show the notification if (accountSettings.containsPersistentCookie() && accountSettings.noAuthExceptionDetected()) { Loading
app/src/main/kotlin/at/bitfire/davdroid/sync/SyncNotificationManager.kt +7 −1 Original line number Diff line number Diff line Loading @@ -33,6 +33,8 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.accountmanager.ui.setup.ReOAuthActivity import foundation.e.accountmanager.utils.AccountHelper import okhttp3.HttpUrl import org.dmfs.tasks.contract.TaskContract import java.io.IOException Loading Loading @@ -124,7 +126,11 @@ class SyncNotificationManager @AssistedInject constructor( val contentIntent: Intent var viewItemAction: NotificationCompat.Action? = null if (e is UnauthorizedException) { contentIntent = Intent(context, AccountSettingsActivity::class.java) contentIntent = if (AccountHelper.isOidcAccount(context, account)) { Intent(context, ReOAuthActivity::class.java) } else { Intent(context, AccountSettingsActivity::class.java) } contentIntent.putExtra( AccountSettingsActivity.EXTRA_ACCOUNT, account Loading
app/src/main/kotlin/foundation/e/accountmanager/ui/setup/ReOAuthActivity.kt 0 → 100644 +95 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 e Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. * */ package foundation.e.accountmanager.ui.setup import android.accounts.Account import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.glance.LocalContext import androidx.hilt.navigation.compose.hiltViewModel import at.bitfire.davdroid.ui.AppTheme import at.bitfire.davdroid.ui.account.AccountSettingsActivity import at.bitfire.davdroid.ui.account.AccountSettingsModel import dagger.hilt.android.AndroidEntryPoint import foundation.e.accountmanager.utils.AccountHelper @AndroidEntryPoint class ReOAuthActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Retrieve the Account from the Intent val account: Account? = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { intent.getParcelableExtra(AccountSettingsActivity.EXTRA_ACCOUNT, Account::class.java) } else { @Suppress("DEPRECATION") intent.getParcelableExtra(AccountSettingsActivity.EXTRA_ACCOUNT) } setContent { AppTheme { if (account != null) { OAuthHandlerScreen( account = account, onFinished = { finish() } ) } else { finish() } } } } } @Composable fun OAuthHandlerScreen( account: Account, onFinished: () -> Unit, ) { val context = LocalContext.current val model = hiltViewModel { factory: AccountSettingsModel.Factory -> factory.create(account) } val authRequestContract = rememberLauncherForActivityResult(model.authorizationContract()) { authResponse -> if (authResponse != null) { model.authenticate(authResponse) // Sync after authenticated AccountHelper.scheduleSyncWithDelay(context) } else { model.authCodeFailed() } onFinished() } // Auto-launch immediately, no UI shown LaunchedEffect(Unit) { val request = model.newAuthorizationRequest() if (request != null) { authRequestContract.launch(request) } else { onFinished() } } }
app/src/main/res/values/e_strings.xml +2 −0 Original line number Diff line number Diff line Loading @@ -34,4 +34,6 @@ <string name="privacy_policy_title_nav">"Privacy Policy"</string> <string name="navigation_drawer_open_webcalmanager">Web Calendar Manager</string> <string name="legacy_murena_login">Legacy Murena.io</string> <string name="sync_error_authentication_oauth">Authentication issue. Tap to sign in again</string> </resources>