Skip to content

Commit e00d4da

Browse files
committed
fix(login): start the browser login again if the pending one was lost
After process death the in-memory pending login is gone, so the recreated BrowserLoginActivity in resume mode only showed an error. The resume intent now also carries the base URL, reauthorize flag and account, and the login is started again from them when there is nothing to continue. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
1 parent 638082a commit e00d4da

5 files changed

Lines changed: 44 additions & 22 deletions

File tree

‎app/src/main/java/com/nextcloud/talk/account/BrowserLoginActivity.kt‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,10 @@ class BrowserLoginActivity : BaseActivity() {
146146
// The account to reauthorize, so a login with another account doesn't change it.
147147
val accountToReauthorize = extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID, 0L).takeIf { it != 0L }
148148

149-
if (extras.getBoolean(BundleKeys.KEY_RESUME_BROWSER_LOGIN, false)) {
150-
viewModel.resumeWebBrowserLogin()
151-
} else if (extras.containsKey(BundleKeys.KEY_FROM_QR)) {
149+
// If the saved login can't be continued, it is started again below with the same extras.
150+
if (extras.getBoolean(BundleKeys.KEY_RESUME_BROWSER_LOGIN, false) && viewModel.resumeWebBrowserLogin()) return
151+
152+
if (extras.containsKey(BundleKeys.KEY_FROM_QR)) {
152153
val uri = extras.getString(BundleKeys.KEY_FROM_QR)!!
153154

154155
if (uri.startsWith(LoginRepository.ONE_TIME_PREFIX)) {

‎app/src/main/java/com/nextcloud/talk/account/data/PendingBrowserLoginStore.kt‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import com.nextcloud.talk.account.data.model.LoginResponse
1414
*/
1515
data class PendingBrowserLogin(
1616
val response: LoginResponse,
17+
val baseUrl: String,
1718
val reAuth: Boolean,
1819
val accountToReauthorize: Long?,
1920
val startedAtMillis: Long
@@ -35,8 +36,8 @@ object PendingBrowserLoginStore {
3536
@Volatile
3637
private var pending: PendingBrowserLogin? = null
3738

38-
fun save(response: LoginResponse, reAuth: Boolean, accountToReauthorize: Long?) {
39-
pending = PendingBrowserLogin(response, reAuth, accountToReauthorize, clock())
39+
fun save(response: LoginResponse, baseUrl: String, reAuth: Boolean, accountToReauthorize: Long?) {
40+
pending = PendingBrowserLogin(response, baseUrl, reAuth, accountToReauthorize, clock())
4041
}
4142

4243
/** The unfinished login, or null if there is none or it expired. */

‎app/src/main/java/com/nextcloud/talk/account/viewmodels/BrowserLoginActivityViewModel.kt‎

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class BrowserLoginActivityViewModel @Inject constructor(val repository: LoginRep
7979
}
8080

8181
// Survives this view model, see PendingBrowserLoginStore.
82-
PendingBrowserLoginStore.save(response, reAuth, accountToReauthorize)
82+
PendingBrowserLoginStore.save(response, baseUrl, reAuth, accountToReauthorize)
8383

8484
_initialLoginRequestState.value =
8585
InitialLoginViewState.InitialLoginRequestSuccess(response.loginUrl)
@@ -89,17 +89,19 @@ class BrowserLoginActivityViewModel @Inject constructor(val repository: LoginRep
8989
/**
9090
* Continues the browser login that a destroyed [com.nextcloud.talk.account.BrowserLoginActivity] left unfinished:
9191
* no new login request and no new browser, only the polling of the saved response.
92+
*
93+
* Returns false if there is nothing to continue (expired, or lost by process death), then the login has to be
94+
* started again.
9295
*/
93-
fun resumeWebBrowserLogin() {
94-
if (!startLoginOnce()) return
95-
val pending = PendingBrowserLoginStore.active()
96-
if (pending == null) {
97-
_postLoginState.value = PostLoginViewState.PostLoginError
98-
return
96+
fun resumeWebBrowserLogin(): Boolean {
97+
if (!isLoginStarted) {
98+
val pending = PendingBrowserLoginStore.active() ?: return false
99+
startLoginOnce()
100+
savedResponse = pending.response
101+
repository.resumeLoginFlow(pending.reAuth, pending.accountToReauthorize)
102+
handleWebBrowserLogin()
99103
}
100-
savedResponse = pending.response
101-
repository.resumeLoginFlow(pending.reAuth, pending.accountToReauthorize)
102-
handleWebBrowserLogin()
104+
return true
103105
}
104106

105107
fun handleWebBrowserLogin() {

‎app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import com.nextcloud.talk.BuildConfig
2626
import com.nextcloud.talk.R
2727
import com.nextcloud.talk.account.BrowserLoginActivity
2828
import com.nextcloud.talk.account.ServerSelectionActivity
29+
import com.nextcloud.talk.account.data.PendingBrowserLogin
2930
import com.nextcloud.talk.account.data.PendingBrowserLoginStore
3031
import com.nextcloud.talk.api.NcApi
3132
import com.nextcloud.talk.application.NextcloudTalkApplication
@@ -120,9 +121,13 @@ class MainActivity :
120121
* The app was opened again (e.g. by the launcher icon) while a login via the browser was unfinished. That
121122
* destroyed the login screens, so they are opened again, with the saved login continued instead of a new one.
122123
*/
123-
private fun resumeBrowserLogin(hasAccounts: Boolean) {
124+
private fun resumeBrowserLogin(pending: PendingBrowserLogin, hasAccounts: Boolean) {
124125
val loginIntent = Intent(context, BrowserLoginActivity::class.java)
125126
loginIntent.putExtra(BundleKeys.KEY_RESUME_BROWSER_LOGIN, true)
127+
// To start the login again if the saved one is lost by process death.
128+
loginIntent.putExtra(BundleKeys.KEY_BASE_URL, pending.baseUrl)
129+
loginIntent.putExtra(BundleKeys.KEY_REAUTHORIZE_ACCOUNT, pending.reAuth)
130+
pending.accountToReauthorize?.let { loginIntent.putExtra(BundleKeys.KEY_INTERNAL_USER_ID, it) }
126131

127132
val screenBelow = when {
128133
hasAccounts -> Intent(context, ConversationsListActivity::class.java)
@@ -301,8 +306,9 @@ class MainActivity :
301306
}
302307
if (isFinishing || isDestroyed) return@launch
303308

304-
if (PendingBrowserLoginStore.active() != null) {
305-
resumeBrowserLogin(users.isNotEmpty())
309+
val pendingBrowserLogin = PendingBrowserLoginStore.active()
310+
if (pendingBrowserLogin != null) {
311+
resumeBrowserLogin(pendingBrowserLogin, users.isNotEmpty())
306312
} else if (users.isNotEmpty()) {
307313
openConversationList()
308314
} else {

‎app/src/test/java/com/nextcloud/talk/account/viewmodels/BrowserLoginActivityViewModelTest.kt‎

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ import kotlinx.coroutines.test.resetMain
1717
import kotlinx.coroutines.test.setMain
1818
import org.junit.After
1919
import org.junit.Assert.assertEquals
20+
import org.junit.Assert.assertFalse
2021
import org.junit.Assert.assertNotNull
2122
import org.junit.Assert.assertNull
23+
import org.junit.Assert.assertTrue
2224
import org.junit.Before
2325
import org.junit.Test
2426
import org.mockito.kotlin.any
@@ -113,8 +115,8 @@ class BrowserLoginActivityViewModelTest {
113115
wheneverBlocking { newRepository.pollLogin(any()) }.thenReturn(null)
114116
val newViewModel = BrowserLoginActivityViewModel(newRepository)
115117

116-
newViewModel.resumeWebBrowserLogin()
117-
newViewModel.resumeWebBrowserLogin()
118+
assertTrue(newViewModel.resumeWebBrowserLogin())
119+
assertTrue(newViewModel.resumeWebBrowserLogin())
118120

119121
verifyBlocking(newRepository, times(1)) { pollLogin(any()) }
120122
}
@@ -149,11 +151,21 @@ class BrowserLoginActivityViewModelTest {
149151
val newRepository: LoginRepository = mock()
150152
val newViewModel = BrowserLoginActivityViewModel(newRepository)
151153

152-
newViewModel.resumeWebBrowserLogin()
154+
assertFalse(newViewModel.resumeWebBrowserLogin())
153155

154156
assertNull(PendingBrowserLoginStore.active())
155157
verifyBlocking(newRepository, never()) { pollLogin(any()) }
156-
assertEquals(BrowserLoginActivityViewModel.PostLoginViewState.PostLoginError, newViewModel.postLoginState.value)
158+
}
159+
160+
@Test
161+
fun `without a saved login the login can be started again`() {
162+
wheneverBlocking { repository.startLoginFlow(any(), any(), anyOrNull()) }.thenReturn(loginResponse)
163+
164+
assertFalse(viewModel.resumeWebBrowserLogin())
165+
viewModel.startWebBrowserLogin(BASE_URL, reAuth = true, accountToReauthorize = ACCOUNT)
166+
167+
verifyBlocking(repository) { startLoginFlow(BASE_URL, true, ACCOUNT) }
168+
assertEquals(BASE_URL, PendingBrowserLoginStore.active()!!.baseUrl)
157169
}
158170

159171
@Test

0 commit comments

Comments
 (0)