From 19b5f1a2abbfe7a2e218d648bfb9ea6e039b22b3 Mon Sep 17 00:00:00 2001 From: zerox80 Date: Fri, 21 Nov 2025 09:42:41 +0100 Subject: [PATCH 01/12] Fix: Critical login state and authentication issues --- opencloudApp/src/main/AndroidManifest.xml | 2 +- .../main/java/eu/opencloud/android/MainApp.kt | 15 +++- .../authentication/AuthenticationViewModel.kt | 6 +- .../authentication/LoginActivity.kt | 71 ++++++++++++++++++- 4 files changed, 87 insertions(+), 7 deletions(-) diff --git a/opencloudApp/src/main/AndroidManifest.xml b/opencloudApp/src/main/AndroidManifest.xml index 93edc6257..0d6ba96aa 100644 --- a/opencloudApp/src/main/AndroidManifest.xml +++ b/opencloudApp/src/main/AndroidManifest.xml @@ -242,7 +242,7 @@ android:name=".presentation.authentication.LoginActivity" android:exported="true" android:label="@string/login_label" - android:launchMode="singleTask" + android:launchMode="singleTop" android:theme="@style/Theme.openCloud.Toolbar"> diff --git a/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt b/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt index 4ac0b4d0f..b31e4b175 100644 --- a/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt +++ b/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt @@ -39,7 +39,7 @@ import android.widget.CheckBox import androidx.appcompat.app.AlertDialog import androidx.core.content.pm.PackageInfoCompat import eu.opencloud.android.data.providers.implementation.OCSharedPreferencesProvider -import eu.opencloud.android.datamodel.ThumbnailsCacheManager + import eu.opencloud.android.db.PreferenceManager import eu.opencloud.android.dependecyinjection.commonModule import eu.opencloud.android.dependecyinjection.localDataSourceModule @@ -99,6 +99,15 @@ class MainApp : Application() { appContext = applicationContext + // Ensure Logcat shows Timber logs in debug builds + if (BuildConfig.DEBUG) { + try { + Timber.plant(Timber.DebugTree()) + } catch (_: Throwable) { + // ignore if already planted + } + } + startLogsIfEnabled() DebugInjector.injectDebugTools(appContext) @@ -108,7 +117,9 @@ class MainApp : Application() { SingleSessionManager.setUserAgent(userAgent) // initialise thumbnails cache on background thread - ThumbnailsCacheManager.InitDiskCacheTask().execute() + // initialise thumbnails cache on background thread + + initDependencyInjection() diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/AuthenticationViewModel.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/AuthenticationViewModel.kt index 03ad1a092..88b03f216 100644 --- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/AuthenticationViewModel.kt +++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/AuthenticationViewModel.kt @@ -72,9 +72,9 @@ class AuthenticationViewModel( private val contextProvider: ContextProvider, ) : ViewModel() { - val codeVerifier: String = OAuthUtils().generateRandomCodeVerifier() - val codeChallenge: String = OAuthUtils().generateCodeChallenge(codeVerifier) - val oidcState: String = OAuthUtils().generateRandomState() + var codeVerifier: String = OAuthUtils().generateRandomCodeVerifier() + var codeChallenge: String = OAuthUtils().generateCodeChallenge(codeVerifier) + var oidcState: String = OAuthUtils().generateRandomState() private val _legacyWebfingerHost = MediatorLiveData>>() val legacyWebfingerHost: LiveData>> = _legacyWebfingerHost diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt index 57ded5f90..5bc09603e 100644 --- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt +++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt @@ -93,6 +93,13 @@ import org.koin.androidx.viewmodel.ext.android.viewModel import timber.log.Timber import java.io.File + + private const val KEY_SERVER_BASE_URL = "KEY_SERVER_BASE_URL" + private const val KEY_OIDC_SUPPORTED = "KEY_OIDC_SUPPORTED" + private const val KEY_CODE_VERIFIER = "KEY_CODE_VERIFIER" + private const val KEY_CODE_CHALLENGE = "KEY_CODE_CHALLENGE" + private const val KEY_OIDC_STATE = "KEY_OIDC_STATE" + class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrustedCertListener, SecurityEnforced { private val authenticationViewModel by viewModel() @@ -114,6 +121,16 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted private var resultBundle: Bundle? = null override fun onCreate(savedInstanceState: Bundle?) { + if (intent.data != null && (intent.data?.getQueryParameter("code") != null || intent.data?.getQueryParameter("error") != null)) { + if (!isTaskRoot) { + val newIntent = Intent(this, LoginActivity::class.java) + newIntent.data = intent.data + newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) + startActivity(newIntent) + finish() + return + } + } super.onCreate(savedInstanceState) checkPasscodeEnforced(this) @@ -134,6 +151,11 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted authenticationViewModel.supportsOAuth2((userAccount as Account).name) } else if (savedInstanceState != null) { authTokenType = savedInstanceState.getString(KEY_AUTH_TOKEN_TYPE) + savedInstanceState.getString(KEY_SERVER_BASE_URL)?.let { serverBaseUrl = it } + oidcSupported = savedInstanceState.getBoolean(KEY_OIDC_SUPPORTED) + savedInstanceState.getString(KEY_CODE_VERIFIER)?.let { authenticationViewModel.codeVerifier = it } + savedInstanceState.getString(KEY_CODE_CHALLENGE)?.let { authenticationViewModel.codeChallenge = it } + savedInstanceState.getString(KEY_OIDC_STATE)?.let { authenticationViewModel.oidcState = it } } // UI initialization @@ -162,6 +184,17 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted binding.accountUsername.setText(username) } } + } else { + // Restore UI state + if (::serverBaseUrl.isInitialized && serverBaseUrl.isNotEmpty()) { + binding.hostUrlInput.setText(serverBaseUrl) + + if (authTokenType == BASIC_TOKEN_TYPE) { + showOrHideBasicAuthFields(shouldBeVisible = true) + } else if (authTokenType == OAUTH_TOKEN_TYPE) { + showOrHideBasicAuthFields(shouldBeVisible = false) + } + } } binding.root.filterTouchesWhenObscured = @@ -192,10 +225,17 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted accountAuthenticatorResponse?.onRequestContinued() initLiveDataObservers() + + if (intent.data != null && (intent.data?.getQueryParameter("code") != null || intent.data?.getQueryParameter("error") != null)) { + if (savedInstanceState == null) { + restoreAuthState() + } + handleGetAuthorizationCodeResponse(intent) + } } private fun handleDeepLink() { - if (intent.data != null) { + if (intent.data != null && intent.data?.getQueryParameter("code") == null && intent.data?.getQueryParameter("error") == null) { authenticationViewModel.launchedFromDeepLink = true if (getAccounts(baseContext).isNotEmpty()) { launchFileDisplayActivity() @@ -467,6 +507,7 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted setResult(Activity.RESULT_OK, intent) authenticationViewModel.discoverAccount(accountName = accountName, discoveryNeeded = loginAction == ACTION_CREATE) + clearAuthState() } private fun loginIsLoading() { @@ -496,6 +537,7 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted } } } + clearAuthState() } /** @@ -551,6 +593,7 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted ) try { + saveAuthState() customTabsIntent.launchUrl( this, authorizationEndpointUri @@ -851,6 +894,10 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putString(KEY_AUTH_TOKEN_TYPE, authTokenType) + if (::serverBaseUrl.isInitialized) { + outState.putString(KEY_SERVER_BASE_URL, serverBaseUrl) + } + outState.putBoolean(KEY_OIDC_SUPPORTED, oidcSupported) } override fun finish() { @@ -871,4 +918,26 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted override fun optionLockSelected(type: LockType) { manageOptionLockSelected(type) } + + private fun saveAuthState() { + val prefs = getSharedPreferences("auth_state", android.content.Context.MODE_PRIVATE) + prefs.edit().apply { + putString(KEY_CODE_VERIFIER, authenticationViewModel.codeVerifier) + putString(KEY_CODE_CHALLENGE, authenticationViewModel.codeChallenge) + putString(KEY_OIDC_STATE, authenticationViewModel.oidcState) + apply() + } + } + + private fun restoreAuthState() { + val prefs = getSharedPreferences("auth_state", android.content.Context.MODE_PRIVATE) + prefs.getString(KEY_CODE_VERIFIER, null)?.let { authenticationViewModel.codeVerifier = it } + prefs.getString(KEY_CODE_CHALLENGE, null)?.let { authenticationViewModel.codeChallenge = it } + prefs.getString(KEY_OIDC_STATE, null)?.let { authenticationViewModel.oidcState = it } + } + + private fun clearAuthState() { + val prefs = getSharedPreferences("auth_state", android.content.Context.MODE_PRIVATE) + prefs.edit().clear().apply() + } } From 1a238ce72305b9516ba7ffb0f628199cb0296b14 Mon Sep 17 00:00:00 2001 From: zerox80 Date: Tue, 25 Nov 2025 00:13:59 +0100 Subject: [PATCH 02/12] feat: Add ClientManager for managing OpenCloud clients and providing various service instances. --- .../opencloud/android/data/ClientManager.kt | 4 +- .../android/data/ClientManagerTest.kt | 65 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 opencloudData/src/test/java/eu/opencloud/android/data/ClientManagerTest.kt diff --git a/opencloudData/src/main/java/eu/opencloud/android/data/ClientManager.kt b/opencloudData/src/main/java/eu/opencloud/android/data/ClientManager.kt index e2356ff11..9d8eb0256 100644 --- a/opencloudData/src/main/java/eu/opencloud/android/data/ClientManager.kt +++ b/opencloudData/src/main/java/eu/opencloud/android/data/ClientManager.kt @@ -91,7 +91,9 @@ class ClientManager( } } else { Timber.d("Reusing anonymous client for ${safeClient.baseUri}") - safeClient + safeClient.apply { + credentials = openCloudCredentials + } } } diff --git a/opencloudData/src/test/java/eu/opencloud/android/data/ClientManagerTest.kt b/opencloudData/src/test/java/eu/opencloud/android/data/ClientManagerTest.kt new file mode 100644 index 000000000..0e028a684 --- /dev/null +++ b/opencloudData/src/test/java/eu/opencloud/android/data/ClientManagerTest.kt @@ -0,0 +1,65 @@ +package eu.opencloud.android.data + +import android.accounts.AccountManager +import android.content.Context +import eu.opencloud.android.data.providers.SharedPreferencesProvider +import eu.opencloud.android.lib.common.ConnectionValidator +import eu.opencloud.android.lib.common.authentication.OpenCloudCredentialsFactory +import io.mockk.mockk +import io.mockk.mockkStatic +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +class ClientManagerTest { + + private val accountManager: AccountManager = mockk() + private val preferencesProvider: SharedPreferencesProvider = mockk() + private val context: Context = mockk(relaxed = true) + private val connectionValidator: ConnectionValidator = mockk() + private lateinit var clientManager: ClientManager + + @Before + fun setUp() { + mockkStatic(android.net.Uri::class) + val uriMock = mockk() + io.mockk.every { android.net.Uri.parse(any()) } returns uriMock + io.mockk.every { uriMock.toString() } returns "https://demo.owncloud.com" + + clientManager = ClientManager( + accountManager, + preferencesProvider, + context, + "eu.opencloud.android.account", + connectionValidator + ) + } + + @org.junit.After + fun tearDown() { + io.mockk.unmockkStatic(android.net.Uri::class) + } + + @Test + fun `getClientForAnonymousCredentials reuses client and resets credentials`() { + val url = "https://demo.owncloud.com" + val mockClient = mockk(relaxed = true) + val uriMock = android.net.Uri.parse(url) + + io.mockk.every { mockClient.baseUri } returns uriMock + + // Inject mock client into clientManager + val field = ClientManager::class.java.getDeclaredField("openCloudClient") + field.isAccessible = true + field.set(clientManager, mockClient) + + // Call method - should reuse mockClient + val resultClient = clientManager.getClientForAnonymousCredentials(url, false) + + assertEquals("Client should be reused", mockClient, resultClient) + + // Verify credentials were set + io.mockk.verify { mockClient.credentials = any() } + } +} From 6aa43f9749f10b09f7b2fbc91005d08c0755b866 Mon Sep 17 00:00:00 2001 From: zerox80 Date: Fri, 9 Jan 2026 22:10:20 +0100 Subject: [PATCH 03/12] Fix: Save OAuth state (codeVerifier, state) in onSaveInstanceState to prevent login failure after process death --- .../android/presentation/authentication/LoginActivity.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt index 5bc09603e..da3d49c2b 100644 --- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt +++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt @@ -898,6 +898,9 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted outState.putString(KEY_SERVER_BASE_URL, serverBaseUrl) } outState.putBoolean(KEY_OIDC_SUPPORTED, oidcSupported) + outState.putString(KEY_CODE_VERIFIER, authenticationViewModel.codeVerifier) + outState.putString(KEY_CODE_CHALLENGE, authenticationViewModel.codeChallenge) + outState.putString(KEY_OIDC_STATE, authenticationViewModel.oidcState) } override fun finish() { From 576955790443e67c1c0228d4334a1a3180614f1c Mon Sep 17 00:00:00 2001 From: zerox80 Date: Fri, 9 Jan 2026 22:23:14 +0100 Subject: [PATCH 04/12] Fix: Add missing super.onCreate call in LoginActivity --- .../android/presentation/authentication/LoginActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt index da3d49c2b..c4ce88120 100644 --- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt +++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt @@ -121,6 +121,7 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted private var resultBundle: Bundle? = null override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) if (intent.data != null && (intent.data?.getQueryParameter("code") != null || intent.data?.getQueryParameter("error") != null)) { if (!isTaskRoot) { val newIntent = Intent(this, LoginActivity::class.java) From 6c5f0f940c6362ff79020db7ad5cdf2b4057067f Mon Sep 17 00:00:00 2001 From: zerox80 Date: Sat, 10 Jan 2026 11:52:44 +0100 Subject: [PATCH 05/12] Fix Firefox OAuth redirect issue - Add FLAG_ACTIVITY_NEW_TASK to CustomTabsIntent for Firefox compatibility - Add setIntent() in onNewIntent to properly handle OAuth redirects - Add logging for OAuth redirect debugging --- .../presentation/authentication/LoginActivity.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt index c4ce88120..fedf925f4 100644 --- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt +++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt @@ -122,8 +122,14 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + // Log OAuth redirect details for debugging (especially Firefox issues) + Timber.d("onCreate called with intent data: ${intent.data}, isTaskRoot: $isTaskRoot") + if (intent.data != null && (intent.data?.getQueryParameter("code") != null || intent.data?.getQueryParameter("error") != null)) { + Timber.d("OAuth redirect detected with code or error parameter") if (!isTaskRoot) { + Timber.d("Not task root, forwarding OAuth redirect to existing LoginActivity instance") val newIntent = Intent(this, LoginActivity::class.java) newIntent.data = intent.data newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) @@ -579,6 +585,11 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted val customTabsBuilder: CustomTabsIntent.Builder = CustomTabsIntent.Builder() val customTabsIntent: CustomTabsIntent = customTabsBuilder.build() + // Add flags to improve compatibility with Firefox and other browsers + // FLAG_ACTIVITY_NEW_TASK ensures the browser opens in a separate task, + // which helps Firefox properly handle the OAuth redirect back to the app + customTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + val authorizationEndpointUri = OAuthUtils.buildAuthorizationRequest( authorizationEndpoint = authorizationEndpoint, redirectUri = OAuthUtils.buildRedirectUri(applicationContext).toString(), @@ -609,6 +620,8 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) intent?.let { + Timber.d("onNewIntent received with data: ${it.data}") + setIntent(it) handleGetAuthorizationCodeResponse(it) } } From 6f70a7e5f27d872f03a30aaf865f9b9e56ec3f02 Mon Sep 17 00:00:00 2001 From: zerox80 Date: Sat, 10 Jan 2026 11:56:39 +0100 Subject: [PATCH 06/12] Fix detekt trailing whitespace and spacing issues --- .../main/java/eu/opencloud/android/MainApp.kt | 2 -- .../authentication/LoginActivity.kt | 17 ++++++++--------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt b/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt index b31e4b175..618dfe376 100644 --- a/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt +++ b/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt @@ -118,8 +118,6 @@ class MainApp : Application() { // initialise thumbnails cache on background thread // initialise thumbnails cache on background thread - - initDependencyInjection() diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt index fedf925f4..084a875bc 100644 --- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt +++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt @@ -93,12 +93,11 @@ import org.koin.androidx.viewmodel.ext.android.viewModel import timber.log.Timber import java.io.File - - private const val KEY_SERVER_BASE_URL = "KEY_SERVER_BASE_URL" - private const val KEY_OIDC_SUPPORTED = "KEY_OIDC_SUPPORTED" - private const val KEY_CODE_VERIFIER = "KEY_CODE_VERIFIER" - private const val KEY_CODE_CHALLENGE = "KEY_CODE_CHALLENGE" - private const val KEY_OIDC_STATE = "KEY_OIDC_STATE" +private const val KEY_SERVER_BASE_URL = "KEY_SERVER_BASE_URL" +private const val KEY_OIDC_SUPPORTED = "KEY_OIDC_SUPPORTED" +private const val KEY_CODE_VERIFIER = "KEY_CODE_VERIFIER" +private const val KEY_CODE_CHALLENGE = "KEY_CODE_CHALLENGE" +private const val KEY_OIDC_STATE = "KEY_OIDC_STATE" class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrustedCertListener, SecurityEnforced { @@ -122,10 +121,10 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - + // Log OAuth redirect details for debugging (especially Firefox issues) Timber.d("onCreate called with intent data: ${intent.data}, isTaskRoot: $isTaskRoot") - + if (intent.data != null && (intent.data?.getQueryParameter("code") != null || intent.data?.getQueryParameter("error") != null)) { Timber.d("OAuth redirect detected with code or error parameter") if (!isTaskRoot) { @@ -195,7 +194,7 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted // Restore UI state if (::serverBaseUrl.isInitialized && serverBaseUrl.isNotEmpty()) { binding.hostUrlInput.setText(serverBaseUrl) - + if (authTokenType == BASIC_TOKEN_TYPE) { showOrHideBasicAuthFields(shouldBeVisible = true) } else if (authTokenType == OAUTH_TOKEN_TYPE) { From 0119d3c9ae356620222a3f8cbf3c3a3ec591c3d5 Mon Sep 17 00:00:00 2001 From: zerox80 Date: Sat, 10 Jan 2026 11:59:52 +0100 Subject: [PATCH 07/12] Fix detekt issues in ClientManagerTest: remove unused imports and trailing whitespace --- .../eu/opencloud/android/data/ClientManagerTest.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/opencloudData/src/test/java/eu/opencloud/android/data/ClientManagerTest.kt b/opencloudData/src/test/java/eu/opencloud/android/data/ClientManagerTest.kt index 0e028a684..5757f96cf 100644 --- a/opencloudData/src/test/java/eu/opencloud/android/data/ClientManagerTest.kt +++ b/opencloudData/src/test/java/eu/opencloud/android/data/ClientManagerTest.kt @@ -4,11 +4,9 @@ import android.accounts.AccountManager import android.content.Context import eu.opencloud.android.data.providers.SharedPreferencesProvider import eu.opencloud.android.lib.common.ConnectionValidator -import eu.opencloud.android.lib.common.authentication.OpenCloudCredentialsFactory import io.mockk.mockk import io.mockk.mockkStatic import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -46,19 +44,19 @@ class ClientManagerTest { val url = "https://demo.owncloud.com" val mockClient = mockk(relaxed = true) val uriMock = android.net.Uri.parse(url) - + io.mockk.every { mockClient.baseUri } returns uriMock // Inject mock client into clientManager val field = ClientManager::class.java.getDeclaredField("openCloudClient") field.isAccessible = true field.set(clientManager, mockClient) - + // Call method - should reuse mockClient val resultClient = clientManager.getClientForAnonymousCredentials(url, false) - + assertEquals("Client should be reused", mockClient, resultClient) - + // Verify credentials were set io.mockk.verify { mockClient.credentials = any() } } From 5586f7ef9274dbc43b10e49e2b5087099f88e1c1 Mon Sep 17 00:00:00 2001 From: zerox80 Date: Mon, 12 Jan 2026 17:38:37 +0100 Subject: [PATCH 08/12] Fix: Remove duplicate super.onCreate call causing SavedStateRegistry crash --- opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt | 3 --- .../android/presentation/authentication/LoginActivity.kt | 1 - 2 files changed, 4 deletions(-) diff --git a/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt b/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt index 618dfe376..1e6c338e4 100644 --- a/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt +++ b/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt @@ -116,9 +116,6 @@ class MainApp : Application() { SingleSessionManager.setUserAgent(userAgent) - // initialise thumbnails cache on background thread - // initialise thumbnails cache on background thread - initDependencyInjection() // register global protection with pass code, pattern lock and biometric lock diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt index 084a875bc..51176ce68 100644 --- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt +++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt @@ -137,7 +137,6 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted return } } - super.onCreate(savedInstanceState) checkPasscodeEnforced(this) From 0aa6aa1885cdbd5b4b818538c543b53ad1962042 Mon Sep 17 00:00:00 2001 From: zerox80 Date: Mon, 12 Jan 2026 17:54:58 +0100 Subject: [PATCH 09/12] Fix: Restore ThumbnailsCacheManager call unintentionally removed --- opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt b/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt index 1e6c338e4..0971bb37c 100644 --- a/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt +++ b/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt @@ -40,6 +40,8 @@ import androidx.appcompat.app.AlertDialog import androidx.core.content.pm.PackageInfoCompat import eu.opencloud.android.data.providers.implementation.OCSharedPreferencesProvider +import eu.opencloud.android.datamodel.ThumbnailsCacheManager +import eu.opencloud.android.datamodel.ThumbnailsCacheManager import eu.opencloud.android.db.PreferenceManager import eu.opencloud.android.dependecyinjection.commonModule import eu.opencloud.android.dependecyinjection.localDataSourceModule @@ -116,6 +118,9 @@ class MainApp : Application() { SingleSessionManager.setUserAgent(userAgent) + // initialise thumbnails cache on background thread + ThumbnailsCacheManager.InitDiskCacheTask().execute() + initDependencyInjection() // register global protection with pass code, pattern lock and biometric lock From f33394ca7c77db6b52f02e4e6b1dac5881a9d152 Mon Sep 17 00:00:00 2001 From: zerox80 Date: Mon, 12 Jan 2026 17:55:14 +0100 Subject: [PATCH 10/12] Fix: Remove duplicate import --- opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt b/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt index 0971bb37c..4897b67c8 100644 --- a/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt +++ b/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt @@ -40,7 +40,6 @@ import androidx.appcompat.app.AlertDialog import androidx.core.content.pm.PackageInfoCompat import eu.opencloud.android.data.providers.implementation.OCSharedPreferencesProvider -import eu.opencloud.android.datamodel.ThumbnailsCacheManager import eu.opencloud.android.datamodel.ThumbnailsCacheManager import eu.opencloud.android.db.PreferenceManager import eu.opencloud.android.dependecyinjection.commonModule From bf69b2825ae1bead7b5af591a19fe76595b30633 Mon Sep 17 00:00:00 2001 From: zerox80 Date: Mon, 12 Jan 2026 18:01:28 +0100 Subject: [PATCH 11/12] Fix: Restore missing Java file and resolve merge conflict --- .../android/presentation/authentication/LoginActivity.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt index 51176ce68..6330ed870 100644 --- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt +++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt @@ -237,6 +237,14 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted } handleGetAuthorizationCodeResponse(intent) } + + // Process any pending intent that arrived before binding was ready + pendingAuthorizationIntent?.let { + handleGetAuthorizationCodeResponse(it) + pendingAuthorizationIntent = null + } + + } private fun handleDeepLink() { From 0dcf9e3ea5b6c94024ee37779fa2cf72c3b07938 Mon Sep 17 00:00:00 2001 From: zerox80 Date: Mon, 12 Jan 2026 18:32:57 +0100 Subject: [PATCH 12/12] Fix: Add missing pendingAuthorizationIntent declaration --- .../android/presentation/authentication/LoginActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt index 6330ed870..4e1819a0a 100644 --- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt +++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt @@ -118,6 +118,7 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted // For handling AbstractAccountAuthenticator responses private var accountAuthenticatorResponse: AccountAuthenticatorResponse? = null private var resultBundle: Bundle? = null + private var pendingAuthorizationIntent: Intent? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)