Skip to content

Commit e02c856

Browse files
committed
Login: Clean back stack with FileDisplayActivity
Before this, we had old activities hanging around
1 parent 9633d3d commit e02c856

2 files changed

Lines changed: 71 additions & 16 deletions

File tree

opencloudApp/src/main/AndroidManifest.xml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
4444
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
4545
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
46+
<uses-permission android:name="android.permission.REORDER_TASKS" />
4647

4748
<application
4849
android:name=".MainApp"
@@ -238,11 +239,18 @@
238239
android:label="@string/pattern_label"
239240
android:theme="@style/Theme.openCloud" />
240241
<activity android:name=".presentation.security.biometric.BiometricActivity" />
242+
<!-- Own taskAffinity + singleTask so LoginActivity always runs in its own task.
243+
During re-auth, startActivityForResult overrides singleTask, so the first
244+
instance still lands in the main task — but the OAuth redirect instance gets
245+
its own task and finishes the orphaned one via a static reference.
246+
autoRemoveFromRecents cleans the login task from recents once it finishes. -->
241247
<activity
242248
android:name=".presentation.authentication.LoginActivity"
249+
android:autoRemoveFromRecents="true"
243250
android:exported="true"
244251
android:label="@string/login_label"
245-
android:launchMode="singleTop"
252+
android:launchMode="singleTask"
253+
android:taskAffinity="eu.opencloud.android.login"
246254
android:theme="@style/Theme.openCloud.Toolbar">
247255
<intent-filter>
248256
<action android:name="android.intent.action.VIEW" />

opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -130,21 +130,9 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
130130
override fun onCreate(savedInstanceState: Bundle?) {
131131
super.onCreate(savedInstanceState)
132132

133-
// Log OAuth redirect details for debugging (especially Firefox issues)
134133
Timber.d("onCreate called with intent data: ${intent.data}, isTaskRoot: $isTaskRoot")
135134

136-
if (intent.data != null && (intent.data?.getQueryParameter("code") != null || intent.data?.getQueryParameter("error") != null)) {
137-
Timber.d("OAuth redirect detected with code or error parameter")
138-
if (!isTaskRoot) {
139-
Timber.d("Not task root, forwarding OAuth redirect to existing LoginActivity instance")
140-
val newIntent = Intent(this, LoginActivity::class.java)
141-
newIntent.data = intent.data
142-
newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
143-
startActivity(newIntent)
144-
finish()
145-
return
146-
}
147-
}
135+
if (handleOAuthRedirectOnCreate()) return
148136

149137
checkPasscodeEnforced(this)
150138

@@ -173,8 +161,7 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
173161

174162
// UI initialization
175163
binding = AccountSetupBinding.inflate(layoutInflater)
176-
val view = binding.root
177-
setContentView(view)
164+
setContentView(binding.root)
178165

179166
if (loginAction != ACTION_CREATE) {
180167
binding.accountUsername.isEnabled = false
@@ -258,8 +245,35 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
258245

259246
// Note: pendingAuthorizationIntent is processed in checkServerType() after
260247
// getServerInfo() completes (process death recovery flow).
248+
}
261249

250+
/**
251+
* If this onCreate is an OAuth redirect, either forward it to the existing instance
252+
* (when not task root) or let it proceed. Otherwise, track this instance so the
253+
* redirect instance can finish it later (see [launchFileDisplayActivity]).
254+
* @return true if onCreate should return early (redirect was forwarded).
255+
*/
256+
private fun handleOAuthRedirectOnCreate(): Boolean {
257+
val hasOAuthData = intent.data != null &&
258+
(intent.data?.getQueryParameter("code") != null || intent.data?.getQueryParameter("error") != null)
262259

260+
if (hasOAuthData) {
261+
Timber.d("OAuth redirect detected with code or error parameter")
262+
if (!isTaskRoot) {
263+
Timber.d("Not task root, forwarding OAuth redirect to existing LoginActivity instance")
264+
val newIntent = Intent(this, LoginActivity::class.java)
265+
newIntent.data = intent.data
266+
newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
267+
startActivity(newIntent)
268+
finish()
269+
return true
270+
}
271+
} else {
272+
// Not an OAuth redirect — track this instance so the redirect instance
273+
// can finish it later (see companion object and launchFileDisplayActivity).
274+
orphanedInstance = java.lang.ref.WeakReference(this)
275+
}
276+
return false
263277
}
264278

265279
private fun handleDeepLink() {
@@ -274,6 +288,29 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
274288
}
275289

276290
private fun launchFileDisplayActivity() {
291+
// Finish the orphaned LoginActivity in the main task (if any).
292+
// During re-auth, startActivityForResult forces the first instance into the main
293+
// task. This second instance (OAuth redirect) runs in its own task and can't reach
294+
// the first via task flags, so we finish it directly via the static reference.
295+
var mainTaskId = -1
296+
orphanedInstance?.get()?.let { other ->
297+
if (other !== this) {
298+
Timber.d("Finishing orphaned LoginActivity in main task")
299+
mainTaskId = other.taskId
300+
other.finish()
301+
}
302+
}
303+
orphanedInstance = null
304+
305+
if (mainTaskId != -1) {
306+
// The main task already has FileDisplayActivity. Bring it to the foreground
307+
// and finish this instance. Just calling finish() would leave the browser on top.
308+
val am = getSystemService(android.content.Context.ACTIVITY_SERVICE) as android.app.ActivityManager
309+
am.moveTaskToFront(mainTaskId, 0)
310+
finish()
311+
return
312+
}
313+
277314
val newIntent = Intent(this, FileDisplayActivity::class.java)
278315
if (authenticationViewModel.launchedFromDeepLink) {
279316
newIntent.data = intent.data
@@ -1127,4 +1164,14 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
11271164
null
11281165
}
11291166
}
1167+
1168+
companion object {
1169+
/**
1170+
* During re-auth, startActivityForResult forces the first LoginActivity into the
1171+
* main task (ignoring singleTask). The OAuth redirect then creates a second instance
1172+
* in its own task. We track the first (orphaned) instance here so the second one
1173+
* can explicitly finish() it after auth completes.
1174+
*/
1175+
private var orphanedInstance: java.lang.ref.WeakReference<LoginActivity>? = null
1176+
}
11301177
}

0 commit comments

Comments
 (0)