@@ -93,6 +93,12 @@ import org.koin.androidx.viewmodel.ext.android.viewModel
9393import timber.log.Timber
9494import java.io.File
9595
96+ private const val KEY_SERVER_BASE_URL = " KEY_SERVER_BASE_URL"
97+ private const val KEY_OIDC_SUPPORTED = " KEY_OIDC_SUPPORTED"
98+ private const val KEY_CODE_VERIFIER = " KEY_CODE_VERIFIER"
99+ private const val KEY_CODE_CHALLENGE = " KEY_CODE_CHALLENGE"
100+ private const val KEY_OIDC_STATE = " KEY_OIDC_STATE"
101+
96102class LoginActivity : AppCompatActivity (), SslUntrustedCertDialog.OnSslUntrustedCertListener, SecurityEnforced {
97103
98104 private val authenticationViewModel by viewModel<AuthenticationViewModel >()
@@ -112,10 +118,27 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
112118 // For handling AbstractAccountAuthenticator responses
113119 private var accountAuthenticatorResponse: AccountAuthenticatorResponse ? = null
114120 private var resultBundle: Bundle ? = null
121+ private var pendingAuthorizationIntent: Intent ? = null
115122
116123 override fun onCreate (savedInstanceState : Bundle ? ) {
117124 super .onCreate(savedInstanceState)
118125
126+ // Log OAuth redirect details for debugging (especially Firefox issues)
127+ Timber .d(" onCreate called with intent data: ${intent.data} , isTaskRoot: $isTaskRoot " )
128+
129+ if (intent.data != null && (intent.data?.getQueryParameter(" code" ) != null || intent.data?.getQueryParameter(" error" ) != null )) {
130+ Timber .d(" OAuth redirect detected with code or error parameter" )
131+ if (! isTaskRoot) {
132+ Timber .d(" Not task root, forwarding OAuth redirect to existing LoginActivity instance" )
133+ val newIntent = Intent (this , LoginActivity ::class .java)
134+ newIntent.data = intent.data
135+ newIntent.addFlags(Intent .FLAG_ACTIVITY_CLEAR_TOP or Intent .FLAG_ACTIVITY_SINGLE_TOP )
136+ startActivity(newIntent)
137+ finish()
138+ return
139+ }
140+ }
141+
119142 checkPasscodeEnforced(this )
120143
121144 // Protection against screen recording
@@ -134,6 +157,11 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
134157 authenticationViewModel.supportsOAuth2((userAccount as Account ).name)
135158 } else if (savedInstanceState != null ) {
136159 authTokenType = savedInstanceState.getString(KEY_AUTH_TOKEN_TYPE )
160+ savedInstanceState.getString(KEY_SERVER_BASE_URL )?.let { serverBaseUrl = it }
161+ oidcSupported = savedInstanceState.getBoolean(KEY_OIDC_SUPPORTED )
162+ savedInstanceState.getString(KEY_CODE_VERIFIER )?.let { authenticationViewModel.codeVerifier = it }
163+ savedInstanceState.getString(KEY_CODE_CHALLENGE )?.let { authenticationViewModel.codeChallenge = it }
164+ savedInstanceState.getString(KEY_OIDC_STATE )?.let { authenticationViewModel.oidcState = it }
137165 }
138166
139167 // UI initialization
@@ -162,6 +190,17 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
162190 binding.accountUsername.setText(username)
163191 }
164192 }
193+ } else {
194+ // Restore UI state
195+ if (::serverBaseUrl.isInitialized && serverBaseUrl.isNotEmpty()) {
196+ binding.hostUrlInput.setText(serverBaseUrl)
197+
198+ if (authTokenType == BASIC_TOKEN_TYPE ) {
199+ showOrHideBasicAuthFields(shouldBeVisible = true )
200+ } else if (authTokenType == OAUTH_TOKEN_TYPE ) {
201+ showOrHideBasicAuthFields(shouldBeVisible = false )
202+ }
203+ }
165204 }
166205
167206 binding.root.filterTouchesWhenObscured =
@@ -192,10 +231,25 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
192231 accountAuthenticatorResponse?.onRequestContinued()
193232
194233 initLiveDataObservers()
234+
235+ if (intent.data != null && (intent.data?.getQueryParameter(" code" ) != null || intent.data?.getQueryParameter(" error" ) != null )) {
236+ if (savedInstanceState == null ) {
237+ restoreAuthState()
238+ }
239+ handleGetAuthorizationCodeResponse(intent)
240+ }
241+
242+ // Process any pending intent that arrived before binding was ready
243+ pendingAuthorizationIntent?.let {
244+ handleGetAuthorizationCodeResponse(it)
245+ pendingAuthorizationIntent = null
246+ }
247+
248+
195249 }
196250
197251 private fun handleDeepLink () {
198- if (intent.data != null ) {
252+ if (intent.data != null && intent.data?.getQueryParameter( " code " ) == null && intent.data?.getQueryParameter( " error " ) == null ) {
199253 authenticationViewModel.launchedFromDeepLink = true
200254 if (getAccounts(baseContext).isNotEmpty()) {
201255 launchFileDisplayActivity()
@@ -467,6 +521,7 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
467521 setResult(Activity .RESULT_OK , intent)
468522
469523 authenticationViewModel.discoverAccount(accountName = accountName, discoveryNeeded = loginAction == ACTION_CREATE )
524+ clearAuthState()
470525 }
471526
472527 private fun loginIsLoading () {
@@ -496,6 +551,7 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
496551 }
497552 }
498553 }
554+ clearAuthState()
499555 }
500556
501557 /* *
@@ -536,6 +592,11 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
536592 val customTabsBuilder: CustomTabsIntent .Builder = CustomTabsIntent .Builder ()
537593 val customTabsIntent: CustomTabsIntent = customTabsBuilder.build()
538594
595+ // Add flags to improve compatibility with Firefox and other browsers
596+ // FLAG_ACTIVITY_NEW_TASK ensures the browser opens in a separate task,
597+ // which helps Firefox properly handle the OAuth redirect back to the app
598+ customTabsIntent.intent.addFlags(Intent .FLAG_ACTIVITY_NEW_TASK )
599+
539600 val authorizationEndpointUri = OAuthUtils .buildAuthorizationRequest(
540601 authorizationEndpoint = authorizationEndpoint,
541602 redirectUri = OAuthUtils .buildRedirectUri(applicationContext).toString(),
@@ -551,6 +612,7 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
551612 )
552613
553614 try {
615+ saveAuthState()
554616 customTabsIntent.launchUrl(
555617 this ,
556618 authorizationEndpointUri
@@ -565,6 +627,8 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
565627 override fun onNewIntent (intent : Intent ? ) {
566628 super .onNewIntent(intent)
567629 intent?.let {
630+ Timber .d(" onNewIntent received with data: ${it.data} " )
631+ setIntent(it)
568632 handleGetAuthorizationCodeResponse(it)
569633 }
570634 }
@@ -851,6 +915,13 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
851915 override fun onSaveInstanceState (outState : Bundle ) {
852916 super .onSaveInstanceState(outState)
853917 outState.putString(KEY_AUTH_TOKEN_TYPE , authTokenType)
918+ if (::serverBaseUrl.isInitialized) {
919+ outState.putString(KEY_SERVER_BASE_URL , serverBaseUrl)
920+ }
921+ outState.putBoolean(KEY_OIDC_SUPPORTED , oidcSupported)
922+ outState.putString(KEY_CODE_VERIFIER , authenticationViewModel.codeVerifier)
923+ outState.putString(KEY_CODE_CHALLENGE , authenticationViewModel.codeChallenge)
924+ outState.putString(KEY_OIDC_STATE , authenticationViewModel.oidcState)
854925 }
855926
856927 override fun finish () {
@@ -871,4 +942,26 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
871942 override fun optionLockSelected (type : LockType ) {
872943 manageOptionLockSelected(type)
873944 }
945+
946+ private fun saveAuthState () {
947+ val prefs = getSharedPreferences(" auth_state" , android.content.Context .MODE_PRIVATE )
948+ prefs.edit().apply {
949+ putString(KEY_CODE_VERIFIER , authenticationViewModel.codeVerifier)
950+ putString(KEY_CODE_CHALLENGE , authenticationViewModel.codeChallenge)
951+ putString(KEY_OIDC_STATE , authenticationViewModel.oidcState)
952+ apply ()
953+ }
954+ }
955+
956+ private fun restoreAuthState () {
957+ val prefs = getSharedPreferences(" auth_state" , android.content.Context .MODE_PRIVATE )
958+ prefs.getString(KEY_CODE_VERIFIER , null )?.let { authenticationViewModel.codeVerifier = it }
959+ prefs.getString(KEY_CODE_CHALLENGE , null )?.let { authenticationViewModel.codeChallenge = it }
960+ prefs.getString(KEY_OIDC_STATE , null )?.let { authenticationViewModel.oidcState = it }
961+ }
962+
963+ private fun clearAuthState () {
964+ val prefs = getSharedPreferences(" auth_state" , android.content.Context .MODE_PRIVATE )
965+ prefs.edit().clear().apply ()
966+ }
874967}
0 commit comments